NaCl非公式ブログ

ブラウザで撮影したカメラの画像をサーバに送信する方法

NaClの佐田です。

今回はブラウザで撮影したカメラの画像をサーバに送信するまでの記事です。 本記事を読まれた方が、カメラの簡単な操作をRailsアプリへ追加できるように紹介します。

本記事をご覧になっている方は、SNSを利用しているでしょうか。 私は携帯で撮影した画像をSNSにアップロードすることがあるので、仕事でカメラを撮影する機能をアプリに追加した時に面白いと感じたので記事にすることにしました。

確認した時の環境は「Chrome バージョン 56.0.2924.87 (64-bit)」です。 画像を撮影するカメラは、タブレット型パソコンにあるカメラ機能を使用しました。

サンプルをGitHubのsada/camera_appリポジトリに置いたので、そのコードを基に紹介していきます。

ブラウザからカメラを使用する許可

ブラウザからカメラを使用する方法は、WebRTC落穂拾い:初心者がつまずきやすいポイントをフォローの記事を参考にしました。 以下のようにカメラを使用するためのJavaScriptを書きました。

var stream;
var video;
navigator.mediaDevices.getUserMedia({video: true, audio: false}).then(function(stream) {
  stream = stream;
  video = document.getElementById('video');
  video.src = window.URL.createObjectURL(stream);
  video.play();
}).catch(function (error) {
  console.error('mediaDevice.getUserMedia() error:', error);
  return;
});

最初に、JavaScriptでカメラの使用許可を得るため、MediaDevices.getUserMedia()を呼び出します。 パソコンと接続しているカメラをブラウザで使用するには、使用許可がないと操作できません。 MediaDevices.getUserMedia()を呼び出すと、カメラの使用許可をブラウザが求めてきます。 URL先の記事では、chrome://flagsでフラグ設定をする必要があるとありましたが、確認した時のバージョンのChromeではフラグ設定をしなくてもカメラが動作しました。

また、カメラの機能自体が有効になっていないと使用許可を求めてきません。 私はVirtualBoxの仮想マシン上の環境で試しましたが、VirtualBoxの[メニュー]の[デバイス]の[Webカメラ]にある[Front Camera]と[Rear Camera]のどちらかが選択されて有効になっていないとカメラの機能が有効になりませんでした。

もちろんカメラの使用許可を求められた時に拒否するとカメラの映像を取得できません。

引数に指定しているvideoやaudioは、ユーザのカメラやマイクへの使用許可を求めるためのオプションの指定です。 MediaDevices.getUserMedia()の戻り値としてPromiseが返されます。 もしカメラの使用が許可された場合は、then関数内でカメラの映像をvideoタグに読み込ませて再生されます。 もしカメラの使用が拒否された場合は、catch関数内の処理が実行されてブラウザのコンソール上にメッセージが表示されます。

カメラの映像を画像データに変換

カメラの映像を表示することができました。 カメラの映像を画像データに変換するには、canvasにカメラの映像を渡してからHTMLCanvasElement.toBlob()を呼び出してBlobオブジェクトにします。

var stream;
var video;
navigator.mediaDevices.getUserMedia({video: true, audio: false}).then(function (stream) {
  stream = stream;
  video = document.getElementById('video');
  video.src = window.URL.createObjectURL(stream);
  video.play();
  setTimeout(function() {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var w = video.offsetWidth;
    var h = video.offsetHeight;
    canvas.setAttribute('width', w);
    canvas.setAttribute('height', h);
    ctx.drawImage(video, 0, 0, w, h);
    canvas.toBlob(function(blob) {
      var img = document.getElementById('image');
      img.src = window.URL.createObjectURL(blob);
    }, 'image/jpeg', 0.95);
    stream.getTracks()[0].stop();
  }, 3000);
}).catch(function (error) {
  console.error('mediaDevice.getUserMedia() error:', error);
  return;
});

setTimeoutを使用して、3秒後にHTMLCanvasElement.toBlob()でカメラの映像をJPEG形式に変換してからimgタグに渡しています。 setTimeoutでカメラの映像の変換を遅らせるのは、そうしないと、カメラの映像が表示される前の黒い画像で画像データとして表示されてしまうからです。

上記までのJavaScriptでは、カメラのサイズを指定していないので、デフォルトのサイズで表示されています。 カメラのサイズを変更するには、以下のようなオプションを指定します。

var constraints = {
  video: {
    mandatory: {
      maxWidth: 320,
      maxHeight: 240
    }
  },
  audio: false
};
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
  // 省略
});

上記のオプションの指定によって、カメラのデフォルトのサイズが640x480で表示されていたのが320x240のサイズに変更されました。 maxWidthとmaxHeightは、カメラの縦横の幅の最大を指定しています。 オプションで指定した値に近い、使用できるカメラのサイズの規格に変更されています。

画像の保存

ここまでカメラの映像を画像データに変換して表示することを紹介しました。 次に、Railsで立ち上げたサーバに送信してデータベースに画像データを保存します。

先ほどはimgタグのsrc属性に渡していたBlobオブジェクトをFormDataに渡してRailsサーバに送信します。

navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
  stream = stream;
  video = document.getElementById('video');
  video.src = window.URL.createObjectURL(stream);
  video.play();
  setTimeout(function() {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var w = video.offsetWidth;
    var h = video.offsetHeight;
    canvas.setAttribute('width', w);
    canvas.setAttribute('height', h);
    ctx.drawImage(video, 0, 0, w, h);
    canvas.toBlob(function(blob) {
      var img = document.getElementById('image');
      img.src = window.URL.createObjectURL(blob);

      var request = new XMLHttpRequest();
      request.open('POST', '/avatars');
      request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
      var formData = new FormData();
      formData.append('avatar[image]', blob, '<%= Time.now.strftime("%Y%m%d%H%M") %>}.jpeg');
      formData.append('avatar[uuid]', '<%= user.uuid %>');
      request.send(formData);
    }, 'image/jpeg', 0.95);
    stream.getTracks()[0].stop();
  }, 3000);
}).catch(function (error) {
  console.error('mediaDevice.getUserMedia() error:', error);
  return;
});

サンプルのRailsアプリでは、画像を保存するgemパッケージとしてpaperclipを使用しました。

外部への公開について

本記事ではbundle exec rails serverコマンドでサンプルのサーバを立ち上げることを想定しています。

もし外部に公開する場合、ChromeはMediaDevices.getUserMedia()を使用しているならばhttpsに設定しないといけません。 Deprecating Powerful Features on Insecure Originsにて書かれています。

まとめ

MediaDevices.getUserMedia()を使用したカメラの画像を撮影することについて書きました。

参考情報

WebRTC落穂拾い:初心者がつまずきやすいポイントをフォロー

camera_new.html

MediaDevices.getUserMedia()

HTMLCanvasElement.toBlob()

Resolution Constraints in Web Real Time Communications draft-alvestrand-constraints-resolution-00

paperclip

Deprecating Powerful Features on Insecure Origins

お仕事駆動でパッチを送ってコードコントリビュート!

NaClの中村です。

OSSに関するイベントの会場などでたまに「OSSにコードコントリビュートしたいんですけどなかなかできなくて…」と聞くことがあります。 私も含め、OSSのイベントに参加されるような方は単にOSSを利用するだけでなく、コードコントリビュートしたい人が多いようです。 確かに自分が書いたコードが広く普及しているOSSに入り、いろんなところで動くのは気持ちのよいものです。

しかし、我々プログラマは日頃のお仕事もあるし、家に帰れば猫の世話に追われ、見たいアニメもある。 ツイッターでは「すごーい!」とつぶやきたいし、とても多忙な日々を過ごしています。 パッチを書いている暇はないのです。

そこでお仕事の時間を利用してパッチを書いてみようというのが今回の趣旨です。 この記事では私が実際にお仕事をしつつパッチを書いた例を見つつ、どのようにOSSにコードコントリビュートするのか紹介したいと思います。

動かないコードの発見

お仕事で以下のような状況がありました。

プロキシ環境

...続きを読む

インターンシップに参加して

はじめまして、2/20 から2週間インターンシップでNaClさんでお世話になってる吉岡です。
普段は神戸にある専門学校に通っているのですがなぜ島根県松江市にあるNaClさんのインターンシップに参加したのかというと

  1. 地元が松江市だったから
  2. Rubyを使ってのお仕事をしている

の2つが主な理由だったりします。

...続きを読む

WebAPIによる周辺施設検索

NaClの石飛です。

全国の様々な場所にライブを見に行くことが趣味です。 そんな時会場までの道順などは調べるのですが周辺の施設についてはあまり調べず、いざ会場まで行ってみるとコンビニがすぐ近くになくて困ることなどもあります。 そこで施設名を渡すと周辺施設の件数や名前をWebAPI呼び出しを通して返してくれるスクリプトを書いてみたので紹介します。

...続きを読む

コマンドによるクリップボードの活用

NaClの野口です。

まず本記事内のクリップボードとはコピー、ペーストをする際に使用する一時領域のことを指します。

端末エミュレータ上での作業の際、クリップボードへ保存(コピー)する領域の選択にマウスを使用することがありますが、以下のような不満を私は感じています。

  • 意図した領域の選択を行うことが難しい
    • 選択領域に過不足があった場合の微調整が難しい
    • 疲れている時には特に難しく、またこの作業を行うことで疲労が更にたまる
  • 繰り返し作業が苦痛
    • 複数行のコピー、ペーストを複数回行う作業は考えるだけで嫌
    • マウス操作は自動化しづらいため楽が出来ない

上記のことを踏まえてマウスを極力使用せず、コマンドを使用してクリップボードを活用する方法について記載します。
また本記事はマウスに触れる時間を極力減らし、キーボードのみでクリップボードの操作を行うことを目標とします。

...続きを読む

Windows上でRuby初心者向け学習環境を作る

NaClの野坂です。

普段は松江市にある本社オフィスで、主にRubyを使ったシステム開発をしつつ、時々Ruby関連のセミナー講師なども務めています。 また、週末は地元のサッカーチームを応援するサポーター活動などしています。

サッカー大好きな私としては、いつか松江市が「サッカーの街」として有名になってくれたらいいなぁ、などと密かに念じていたり しますが、やっぱり業界的には「Rubyの街」という印象が圧倒的かと思います。 実際、地元のクラブの試合を応援していても、ハーフタイムの雑談中に、IT業界とは無縁なはずのサポーター仲間から、

「Rubyってヤツを勉強しようと思ったら、どう始めればいいの?」

なんて普通に聞かれたりする街です。

本稿では、そんなRuby初心者に実際にRubyが実行できる環境を提供する方法についてご紹介したいと思います。

...続きを読む

Amazon S3上のファイルをX-Sendfileで配信する

NaClの田中です。

Amazon S3に格納したファイルを、X-Sendfileを使って配信する仕組みを構築しました。この記事ではその実現方法を紹介します。

X-Sendfileとは?

X-Sendfileとは、NGINXのドキュメントによると「認証、ロギングなどをバックエンドで処理した後、内部リダイレクトされた場所からエンドユーザにコンテンツを配信するようにWebサーバが処理することで、バックエンドを解放して他の要求を処理させる仕組み」だそうです。Webサーバにコンテンツ配信をさせ、バックエンドのスループットを向上させるための機能、ということですね。
詳しい利用方法についてはドキュメントを参照ください。

AmazonS3上のファイル配信

さて本題です。今回やりたかったことはAmazonS3上にあるファイルの配信です。

...続きを読む

APNGの構造とRubyでの読み書き

(注) 本稿執筆時点の2016年12月現在、APNGはFirefoxやSafariなどの一部ブラウザでのみアニメーションが再生されます。

NaCl松江本社の諸星です。絵を描くのが趣味です。
様々な都合があることは理解しつつも、SNSにアップロードしたPNG画像が自動でJPEGに変換されると悲しい気持ちになります。

ところでみなさんはAPNGについてご存知でしょうか。

Animated PNG、すなわちアニメーションするPNG画像のことで、要は動画を扱うことを目的としたファイルフォーマットです。みんな大好きGIFアニメみたいなものですね。

APNGを巡る比較的最近の動向として、2016年6月にAPNGを使ったアニメーションLINEスタンプ作成が一般クリエイターにも開放されるという出来事がありました。 これを受けてか、グラフィックソフトCLIP STUDIO PAINTでも2016年10月末に公開されたVer.1.6.3からAPNG形式での書き出しに対応しています。

このようなかたちで今後じわじわと利用が広がっていくことを期待しつつAPNGの構造について調べてみましたので、以下の構成でまとめたいと思います。

  • APNGとは?
    • GIFとは何が違うの?
  • PNGとの違い
    • PNGの構造
    • APNGの構造
  • RubyでAPNGを読み書きしてみよう
    • ChunkyPNGを拡張してAPNGを読み書き
...続きを読む

細かすぎて見つからなかったmrubyの非互換

こんにちは。yhara@NaCl松江本社です。本記事はmruby Advent Calendar 2016の6日目のエントリです。

mrubyとは

mrubyは組み込み用途のために作られたRuby処理系です。言語仕様としてはCRubyとほぼ同じですが、BignumやRegexpがないなど一部に制約があります。mrubyにはmrbgemsというRubyGemsに似た仕組みがあるため、足らない機能はmrbgemsによって補うことができます。

用途としてはハードウェアへの組み込みの他、nginxのようなソフトウェアに組み込んだり、あるいはmrubyプログラムを単一の実行のバイナリにすることができるという特徴からMItamaeのようにサーバ管理の文脈で名前を聞くことも増えています。

CRubyとの互換性

mrubyのコードベースはCRubyとは独立しているため、CRubyにはないバグが存在していることもあります。例えばCRubyのtest/basictestの中にも、mrubyでは動かないものがありました。以下はそれらの、細かすぎて今まで発見されていなかった非互換をまとめたものです。特に実生活に役立ったりはしないと思いますが、「うわー細かい…」と思いながら見てもらえればと思います。

...続きを読む

VagrantのDocker Provider

西田雄也と申します. 長女(5)に嫌われているのか3歳の中頃からお風呂に誘うと「お父さんやだ.一人で入る.」と言われます.

本稿の想定読者: Vagrantを使っている方

開発環境を作成するのに便利なVagrantですが,2014-05にリリースされた1.6からはDocker Providerが標準で使えるようになっています.これはVirtualBoxの代わりにDockerを使って同様の環境を用意するもので,VirtualBoxよりもDockerの方が隔離レベルが低い分,仮想環境の構築・起動を短時間で行える利点があります.

筆者は作成したItamaeレシピを動かしたり,レシピの処理結果をServerspecで試験する環境として使っています.

本稿ではそんなVagrantのDocker Providerの使い方を紹介します.

...続きを読む