MotoJapan's Tech-Memo

技術めも

【CSS】接頭句の違い "#"と"." 【お家IT#15】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は、ボタンの透過度を設定した。
今回は、基礎的な話でなんとなく使っていたCSSの接頭句の違いを書く。


前々回のコードをコピーしたのが下記。

"."で開始するstyleは、classに対応
"#"で開始するstyleは、idに対応

classで記載しておくと同じstyleを適用できる。

//**************** html **********************

…
    <div class="l2">
        <canvas id="camera_record_box"></canvas>
    </div>
…

//**************** css ***********************


<style type="text/css">.l2 {
    top: 0px;   /*動的に変更されるので適当*/
    left: 0px;  /*動的に変更されるので適当*/
    width: 400px; /*撮影枠サイズ*/ 
    height: 60px; /*撮影枠サイズ*/
    position: absolute; /*absoluteに設定*/
    padding: 0em 0em;
}
#camera_record_box{
    position: absolute; /*absoluteに設定*/
    border: 1px solid red; /*赤い撮影枠*/
    padding: 0em 0em;
}</style>

以上。
次回でフロントエンド(webアプリ系統)の話は終えたい。
最後は簡単に非同期処理について、以降は画像処理の話をしたい。

【CSS】半透明(透明度)の設定方法 【お家IT#14】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次


前回は、レイアウトの重ね合わせを実装した。
今回は、ボタンの透過度を設定したい。

完成イメージ

前回からの続きだが、ボタンが透過して下レイヤーの映像をスルーするようにしたい。
f:id:motojapan:20171029201448p:plain

実装

opacityで設定することができる。
前回までに追記。

.my_btn{
    …
    background: #668ad8;
    opacity: 0.8; /*0-1の範囲で不透明度を設定*/
    …
}

結果

f:id:motojapan:20171029200413j:plain

以上。

次回はなんとなく使っていたCSSの接頭句についてまとめる。

【Javascript / CSS】divタグでcanvas等のレイアウトを重ねる方法 【お家IT#13】


本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は、カメラ撮影枠を作成するために、カメラリソースの切り替えによるVideo要素のサイズ変化を検出した。
今回は、実際にカメラ撮影枠を作るためのレイアウトの重ね合わせ方法についてまとめる。

完成イメージ

こんなものを作りたい。
f:id:motojapan:20171029201525p:plain
カメラのプレビュー上に、撮影領域と撮影ボタンを配置したい。

基本方針

  • 基本的な枠組みは、HTMLのdiv要素CSSでレイアウトの重ね合わせ。
  • 動的なレイアウトプロパティの変更は、JavascriptからCSSの設定を変更。

div要素で重ねわせ方法と実装

ポイント

重ねわせで重要なのはCSSのposition。
下記の設定を行う。

外側のparent position: relativeに設定。
内側のl1~l3 特定の位置に置きたい場合、position: absoluteに設定。

特に今回は、

  • l1は、video要素があり、動画ストリームが確定すると自動的にサイズが決まる
  • l2は、l1のサイズのサイズに応じて、動的にサイズが決まる

となる。

実装

動的にCSSプロパティを操作する場合、2つの方法がある(style / setAttribute)
実装は下記の通り。

//**************** html **********************

<div class="parent" width="720px">
    <div class="l1">
        <video id="camera" width="720px" autoplay></video>
    </div>
    <div class="l2">
        <canvas id="camera_record_box"></canvas>
    </div>
    <div class="l3">
        <a id="take_picture" href="#" class="my_btn">TAKE PICTURE</a>
    </div>
</div>

//**************** css ***********************

<style type="text/css">
.parent {
    width: 720px;
    height: 0px;
    position: relative; /*relativeに設定*/
    border: 3px solid black;
    padding: 0em 0em;
    margin:0;
}
.l1 { /*前回の話の通り、動画ストリームが確定してから自動的にサイズが決まる*/
    top: 0px;
    left: 0px;
    width: 720px; 
    padding: 0em 0em;
}
.l2 {
    top: 0px;   /*動的に変更されるので適当*/
    left: 0px;  /*動的に変更されるので適当*/
    width: 400px; /*撮影枠サイズ*/ 
    height: 60px; /*撮影枠サイズ*/
    position: absolute; /*absoluteに設定*/
    padding: 0em 0em;
}
.l3 {
    bottom: 0px; /*下端に配置*/
    position: absolute; /*absoluteに設定*/
    padding: 0em 0em;
}
#camera_record_box{
    position: absolute; /*absoluteに設定*/
    border: 1px solid red; /*赤い撮影枠*/
    padding: 0em 0em;
}
</style>

//************ javascript ********************

//関数でCSSの設定を操作する
//(前回に追記)

<script type="text/javascript">
function notify_change_size() {
    video_layout_width  = video.clientWidth
    video_layout_height = video.clientHeight

    var element_parent = document.getElementsByClassName('parent');
    var element_l2  = document.getElementsByClassName('l2');
    var element_box = document.getElementById('camera_record_box');

    box_width  = 400 //box size
    box_height = 60  //box size

    left = (video_layout_width - box_width) / 2
    top = (video_layout_height - box_height) / 2

    //parentへの設定
    for(var i=0, l=element_parent.length; i<l; i++){
        element_parent[i].style.width   =  String(video_layout_width)  + "px";
        element_parent[i].style.height  =  String(video_layout_height) + "px";
    }

    //l2への設定
    for(var i=0, l=element_l2.length; i<l; i++){
        //今回はi=0のみ
        element_l2[i].style.left =  String(left)  + "px";
        element_l2[i].style.top  =  String(top)   + "px";
        element_l2[i].style.width   =  String(box_width)  + "px";
        element_l2[i].style.height  =  String(box_height) + "px";
    }

    //camera_record_boxへの設定
    element_box.setAttribute("width",  String(box_width)  + "px");
    element_box.setAttribute("height", String(box_height) + "px");
}

videoElement.onresize = notify_change_size;

</script>

結果

上手くいった。
f:id:motojapan:20171029200239j:plain


以上。

次回はボタンを透過したい。

【Javascript】カメラ起動前後のvideoタグのサイズ変化を検出する 【お家IT#12】


本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は、UserMediaのカメラリソースを切り替えるなどの話をした。
今回は、カメラリソースの切り替えによるVideo要素のサイズ変化を検出したい。
例えばインカムからアウトカムに切り替えるとき、解像度が異なると、Video要素のサイズが変わる。
f:id:motojapan:20171024003838p:plain

もう少しわかりやすく具体例を出して説明する。

具体例:Video要素のプレビューの中央に撮影領域を作りたい

例えば、下図のようにVideo要素のプレビューの中央に撮影領域を作りたい。(赤い枠線部分)
f:id:motojapan:20171023005112p:plain
これを作る場合、少なくともVideo要素のサイズ変化を検出してレイアウト確定時のサイズを求める必要がある。

実装:Video要素のサイズ変化を検出、確定時のサイズを取得

実は知っていればめちゃくちゃ簡単

//**************** html **********************

<video id="camera" width="720px" autoplay></video>

//************ javascript ********************

//動的に取得する関数
<script type="text/javascript">
function notify_change_size() {
    var video = document.getElementById('camera');
    video_layout_width  = video.clientWidth
    video_layout_height = video.clientHeight
}

var videoElement = document.querySelector('video#camera');
//onresizeメッセージのコールバック関数を設定する
videoElement.onresize = notify_change_size;
</script>

以上。
次回はこの具体例を少し掘り下げてどうやってVideo要素でのプレビュー上に撮影領域を作るか考える。

【Javascript】カメラ(インカメラ/アウトカメラ)を切り替える方法 【お家IT#11】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp


目次

前回からWebカメラ周り(Video要素やwebRTC, UserMedia)の操作を進めていたが、カメラリソース周りを今回進める。
やりたいことはWebアプリでのスマホカメラ起動なのでフロントカメラ、バックカメラの切り替えを行いたい。

参考資料

この為にいろいろ調べたけど、パッとした記事はあまりなく結局下記の資料に行き着いた。
ここを読めば大体使い方のイメージはつく。

Demo
webrtc.github.io

Github
github.com

というこれ本家。

MediaDeviceInfoの解説とUserMediaの使い方手順

上を読めばカメラだけでなくマイク、スピーカーのUserMedia周りの実装が分かるし、私が纏めるまでもないが、
重要なのは、”navigator.mediaDevices.enumerateDevices()”

この関数の戻り値は、フロントエンド側でアクセス可能なI/Oメディアデバイスの情報(MediaDeviceInfoオブジェクトの配列)である。(Promise Object)
MediaDeviceInfoには下記の情報が含まれている。

label バイス名(Macなら”FaceTime HD Cam” とか)
deviceId 同一セッション内で一貫したデバイスID (セッションが切れるとrefresh)
groupId 物理デバイス単位でのグループID
kind videoinput / audioinput / audiooutput

手順としては次の3つ
1) navigator.mediaDevices.enumerateDevices()でdeviceIdを取得
2) 1をvideoのdeviceIdと指定し、navigator.mediaDevices.getUserMediaでstreamを取得
3) 2のstreamをwindow.stream / video elementのstreamに代入


以上。

次回は、カメラ起動前後のvideoタグのサイズ変化を検出する。

【Javascript】video/canvasを上下180度反転(回転)させる方法 【お家IT#10】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 

前回までの投稿でバックエンドとフロントエンドの通信周りは整理した。
ここからはWebカメラの操作を進める。

今回は、video/canvasを上下180度反転(回転)させるたい。
原因は、Windowsタブレットのカメラが上下反転して表示されてしまう。(ハードウェア原因)
スマホなら問題ないのだが、気持ち悪いので補正する。

Videoの回転

HTML5&CSS3の組み合わせで、webkitのtransform rotationを使えば回転可能
回転中心は、Video領域の中心である

//**************** html **********************

<video id="camera" width="720px" autoplay></video>

//**************** css ***********************

<style type="text/css">
.Rotate0{
    -webkit-transform: rotate(0deg);
}
.Rotate180{
    -webkit-transform: rotate(180deg);
}
</style>

//************ javascript ********************

//関数から動的に変更する
<script type="text/javascript">
function rotate_video(degree) {
    var video = document.getElementById('camera');
    //スタイルを適用する関数を定義
    video.className = "Rotate"+degree;
}

//元の画像から180度反転する
rotate_video(180)
//元の画像に戻る
rotate_video(0)
</script>

Canvasの回転

Canvasの回転はテクニックが必要で、
単純にCanvasのcontextをrotateすると回転中心が(x, y) = (0, 0)つまり、左上部になってしまい、Canvasの中心では回転しない
なので、translate(移動1) -> rotate(回転) -> translate(移動2)の手順で意図した回転になる。
移動1は、Canvas中心が(x, y) = (0, 0)になるように、画像半分の縦横長で移動
移動2は、Canvas中心が移動1実行前の位置に戻す為に、移動1と同じだけ反対方向に移動

今回はcanvasを回転させてから、videoタグの画像を張り付けるようにします。

//**************** html **********************

<canvas id="canvas"></canvas>

//**************** css ***********************

//不要

//************ javascript ********************

//関数から動的に変更する
<script type="text/javascript">
function rotate_canvas(degree) {
    var canvas = document.getElementById('canvas');
    //canvasの描画モードを2sに
    var ctx = canvas.getContext('2d');
 
   //Canvasに設定するサイズを決める
    //(例えば、videoの縦幅横幅を取得)
    var video = document.getElementById('camera');
    var w = video.offsetWidth;
    var h = video.offsetHeight;

    //同じサイズをcanvasに指定
    canvas.setAttribute("width", w);
    canvas.setAttribute("height", h);

    //一旦canvasをリセットしたければ
    //ctx.clearRect(0, 0, canvas.width, canvas.height);

    //[移動1] Canvas中心が(x, y) = (0, 0)になるように、画像半分の縦横長で移動
    ctx.translate(w/2, h/2);
    //[回転]
    ctx.rotate(degree * Math.PI / 180);
    //[移動2] Canvas中心が移動1実行前の位置に戻す為に、移動1と同じだけ反対方向に移動
    ctx.translate( -1 * w/2, -1 * h/2 );    

    //videoの画像をcanvasにコピー
    ctx.drawImage(video, 0, 0, w, h);
}

//元の画像から180度反転する
rotate_canvas(180)
//元の画像に戻る
rotate_canvas(0)
</script>

以上。
次回はカメラ切り替えを予定。

【flask to Javascript】jsonデータを送信/受信する方法(jsonify) 【お家IT#9】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 

前回までで、javascript->flaskへbase64を送信した。
これを画像処理して、その結果を返信するときjsonだと汎用性も高い。
なのでjsonデータの返信方法をメモ。

送信側実装 (python:flask)

flaskのjsonifyを使うと簡単。

from flask import Flask, request, abort, make_response, current_app, jsonify

...
@app.route('/hoge_image_processing', methods=['POST'])
@crossdomain(origin='*')
def image_processing():

    ...

    res = {
        'ip_type' : 'OCR',           # 画像処理タイプ
        'result'  : 'hogehoge',      # 画像結果
        'prefix'  : '201701011200'   # 時刻プレフィクス
    }    
    
    return jsonify(ResultSet=res)
...

受信側実装 (javascript)

いままでの続きに追記です。
重複部分はある程度省略。

#ajax用モジュール読み込み
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    ...

    function send_img(){
        ...

        #ajax送信
        $.ajax({
            //画像処理サーバーに返す場合
            url: 'https://192.168.0.100:12345/hoge_image_processing',   
            type: 'POST',
            data: fData ,
            contentType: false,
            processData: false,
            success: function(data, dataType) {
                //非同期で通信成功時に読み出される [200 OK 時]
                //console.log('Success', data);
                if (data.ResultSet.ip_type == 'OCR') {
                    var res    = data.ResultSet.result; // 'hogehoge'
                    var prefix = data.ResultSet.prefix; // '201701011200'

                }

            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                //非同期で通信失敗時に読み出される
                console.log('Error : ' + errorThrown);
            }
        });
    }
    ...
</script>

以上。

ここまでの4回の投稿でバックエンドとフロントエンドの通信周りは整理されたので、次回からはWebカメラの操作(反転、カメラ切り替え、サイズ変化検出)を進める。