FabricJS

【JavaScript】【Fabric.js】回転させた画像をキャンバスの背景画像に設定する

JPEG画像は画像の回転角度(元の画像から何度回転したか)をEXIF情報内のOrientationに保持している。

この回転角度を意識せずにWEBアプリでアップロードする画像の描画等を行うと、想定とは異なる向きの画像が表示されたりする。

今回は、キャンバスの背景画像に回転させた画像を設定していく。

html上のimgタグで回転表示させるだけであれば、transformに適切な角度を設定するだけで上手く表示できるが、キャンバス上の場合、それだけでは上手く表示ができない。

適切な個所に表示させるためには、回転角度に応じて、originの設定を変更してから回転させる必要がある。

HTML(画像読込フォーム)

画像ファイルはフォームから読み込んだものを設定する。

<form>
  <!-- 画像読込フォーム -->
  <input type="file" id="selectImage" name="selectImage" accept="image/*" >
</form>

<!-- キャンバス -->
<canvas class="imageCanvas" id="imageCanvas"></canvas>

htmlについては、一先ず、画像読込部分とキャンバス部分のみ記載する。

JavaScript部分は以下の通り。

画像読込開始時の処理

/*
 * ボタンからの画像ファイルの読込
 */
document.getElementById('selectImage')
        .addEventListener('change',async function (e) {

    //画像ファイル用変数
    let imgFile = e.target.files[0];

    //画像ファイル選択判定
    if (imgFile != null ) {
        drawBackgroundImage(imgFile);
    }else {
});

画像選択時に実行される処理。メイン処理と分けたのは、ネストが深くなるのを避けるため。

キャンバス描画メイン処理

/*
 * drawBackgroundImage()
 * キャンバスの背景に画像を描画する。
 */
async function drawBackgroundImage(imgFile) {
    //フォームから読み込んだ画像データをキャンバスの背景に描画する。

    //exif情報の取得
    let orientation = await getOrientation(imgFile);
    console.log("orientation:", orientation);

    //回転角度の設定
    let imgAngle = setAngle(orientation);

    //画像からblobUrlを作成し、背景画像として表示する。
    let blobUrl = window.URL.createObjectURL(imgFile);

    //blobURLから画像データ読込
    let imgElement = await loadImage(blobUrl).catch(e => {
        console.log("onload error", e);
    });

    //画像の縦横サイズ取得
    let imgSize = {
            "imgHeight": 0,    //初期化
            "imgWidth": 0      //初期化
    }
    imgSize = getImgSize(imgElement, imgAngle);

    //キャンバスのサイズを画像のサイズに変更する。
    canvas.setWidth(imgSize.imgWidth);
    canvas.setHeight(imgSize.imgHeight);

    //画像の原点の設定
    let imgOrigin = {
            "imgOriginX": "",     //初期化
            "imgOriginY": ""      //初期化
    }
    imgOrigin = getImgOrigin(imgAngle);

    //キャンバスの背景に画像描画
    canvas.setBackgroundImage(blobUrl, canvas.renderAll.bind(canvas), {
        backgroundImageOpacity: 0,
        backgroundImageStretch: false,
        originX: imgOrigin.imgOriginX,
        originY: imgOrigin.imgOriginY,
        angle: imgAngle
    });
    console.log("drawBackgroundImage end");
}

EXIF情報の取得、回転角度の設定、画像データの読込、画像に合わせたキャンバスサイズの設定、背景画像の原点設定、キャンバスに背景描画と処理を行っていく。

フォームからの画像読込やexif情報の取得は非同期で行われるため、Promiseとasync/awaitを使う。

EXIF情報の取得

/*
 * getOrientation
 * 戻り値
 *   resolve: exifdata.Orientationを返す
 *   reject: なし
 */
function getOrientation(imgFile){
    return new Promise( (resolve, reject) => {
        EXIF.getData(imgFile, function() {
            console.log(imgFile.exifdata);
            resolve(imgFile.exifdata.Orientation);
        });
        //EXIFデータの取得完了時以外は何も返さない。
    });
}

Promiseでresolveのみ返すようにする。

回転角度の設定

/*
 * setAngle
 * orientationの値に応じて回転角度を返却する。
 * 引数
 *   orientation: exif.Orientation
 * 戻り値
 *   imgAngle: 正しい向きに戻すための回転角度(時計回り)
 */
function setAngle(orientation){
    // orientationの値に応じて回転角度を返却する。
    switch(orientation){
    case 1:        //回転なし
        imgAngle = 0;
        break;
    case 3:        //元画像を180度回転している。
        imgAngle = 180;
        break;
    case 6:        //元画像を270度回転している。
        imgAngle = 90;
        break;
    case 8:        //元画像を90度回転している。
        imgAngle = 270;
        break;
    default:
        imgAngle = 0;
        break;
    }
    return imgAngle;
}

orientationの値に応じて、回転させる角度を返す。

画像データ読込

/*
 * 画像読込時用のPromiseを返す関数
 * 戻り値
 *   resolve:画像データ
 *   reject :error情報
 */
function loadImage(blobImg){
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = (e) => reject(e);
        img.src = blobImg;
    });
}

画像を読み込むと、画像サイズを取得できるため、キャンバスサイズを設定していく。

画像サイズの取得

/*
 * 画像のサイズを取得する。
 */
function getImgSize(imgElement, imgAngle){
    //変数宣言と初期化
    let imgSize = {
            "imgHeight": 0,
            "imgWidth": 0
    }

    //画像の回転角度に合わせて、画像の縦横を取得する。
    if ((imgAngle === 90) || (imgAngle === 270)) {
        console.log("convert height and width");
        imgSize.imgHeight = imgElement.naturalWidth;
        imgSize.imgWidth = imgElement.naturalHeight;
    } else {
        imgSize.imgHeight = imgElement.naturalHeight;
        imgSize.imgWidth = imgElement.naturalWidth;
    }
    return imgSize;
}

回転角度が90度、270度の場合、回転後に縦と横が入れ替わるので、それを考慮してキャンバスサイズを設定する。

背景画像の原点設定

/*
 * 画像の回転角度に合わせてOriginX、OriginYを設定する。
 */
function getImgOrigin(imgAngle){
    //変数宣言と初期化
    let imgOrigin = {
            "imgOriginX": "",
            "imgOriginY": ""
    }

    //回転角度に応じてOriginを設定する。
    switch(imgAngle){
    case 0:
        imgOrigin.imgOriginX = "left";
        imgOrigin.imgOriginY = "top";
        break;
    case 90:    //左下を起点に回転させる
        imgOrigin.imgOriginX = "left";
        imgOrigin.imgOriginY = "bottom";
        break;
    case 180:    //右下を起点に回転させる
        imgOrigin.imgOriginX = "right";
        imgOrigin.imgOriginY = "bottom";
        break;
    case 270:    //右上を起点に回転させる
        imgOrigin.imgOriginX = "right";
        imgOrigin.imgOriginY = "top";
        break;
    default:
        imgOrigin.imgOriginX = "left";
        imgOrigin.imgOriginY = "top";
        break;
    }
    return imgOrigin;
}

回転させる時の原点の設定を誤ると、回転後にキャンバス上に画像が見えないということが起こる。

imgタグだとこんなことを考慮しなくて良いのだが、キャンバスの場合、考慮する必要がある。

WEBサーバにアップロードしてから画面に表示でもOKな場合、サーバ側で回転させて、キャンバス上は普通に表示するのが楽だと思う。

そもそも、画像を色々加工して表示したいとか、理由がない限り、画像描画にキャンバスは使う必要がない。というか、面倒なだけw