MENU

Canvas画像処理DOM

【JavaScript】 Canvas描画で特殊な合成をおこなうglobalCompositeOperation

更新日:2020/06/03

 

Canvas APIは画像描画時に globalCompositeOperationを使用すると、元からある画像との合成方法を指定することができます。

 

ここでは、globalCompositeOperationに指定できる値と結果を一覧にしてお伝えします。

 

 

参考記事:【JavaScript】 Canvasの使い方まとめ

デモ

 

まずは合成のイメージをお伝えするために、globalCompositeOperationの簡単なデモを用意しました。

 

 

画像合成設定値:

 

テキスト合成設定値:

 

globalCompositeOperation デモ用画像1globalCompositeOperation デモ用画像2

 

 

参考:デモhtml

<canvas id="cvdemo" width="630" height="400" style="max-width:100%"></canvas>

 

画像合成設定値:<select id="cvsel1"></select>

 

テキスト合成設定値:<select id="cvsel2"></select>

 

 

参考:デモJavaScript

window.addEventListener('DOMContentLoaded',()=> {

 

    const dcv = document.getElementById('cvdemo');
    const context = dcv.getContext('2d');

 

            // 選択リスト オプション内容作成
    const lists = ( ( selLists , selValue ) => selLists.map( 
         e => {
            const list = document.getElementById(e);
            const fragment = document.createDocumentFragment();

 

            selValue.forEach( e =>{
                    const opt = document.createElement("option");
                    opt.value = e;
                    opt.innerHTML = e;
                    fragment.appendChild( opt );
            });

 

            list.appendChild(fragment);
            list.value = selValue[0];
            list.disabled = true;
            return list;

 

        })) ( ["cvsel1","cvsel2"] , [
                  "source-over","source-atop","source-in","source-out","destination-over","destination-atop",
                  "destination-in","destination-out","lighter","copy","xor","normal","multiply","screen",
                  "overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light",
                  "difference","hue","saturation","color","luminosity"
                ]);

 

               // 描画処理
        const setImage = () => {
                lists.forEach( e => e.disabled = false );
                context.clearRect(0,0,630,400);
                context.save();
                context.drawImage( image[0] , 0, 0);
                context.globalCompositeOperation = lists[0].value;
                context.drawImage( image[1] , 0, 0);
                context.globalCompositeOperation = lists[1].value;
                context.font = "40px sans-serif";
                context.fillStyle = "orange";
                context.fillText("globalCompositeOperationデモ", 10, 200);
                context.restore();
        };
    
                // イメージオブジェクト作成 & 画像読み込み待ち
    const image = (( imagePath ) => {
        const im = imagePath.map( e => new Image());
        im.forEach( e => e.onload = () => im.every( e => e.complete  ) ? setImage() : null );
        im.forEach( ( e , i ) => e.src = imagePath[i] );
        return im;
    })( ["image01.jpg","image02.png"] );  

 

                // リスト選択イベント登録
    lists.forEach( e => e.addEventListener("change", ()=>setImage() ) );
    
});

globalCompositeOperationの指定方法

 

globalCompositeOperationを使用しない場合、次のように描画した順番で上書きされます。

 

JavaScript

 

context.fillStyle = "blue";
context.fillRect( 0 , 0 , 100 , 100);
context.fillStyle = "red";
context.fillRect( 50 , 20 , 100 , 100);
context.fillStyle = "green";
context.fillRect( 70 , 40 , 100 , 100);

 

globalCompositeOperationを使用しない場合

 

 

次のようにglobalCompositeOperationに合成方法をセットすることで、描画結果を変更することができます。

 

JavaScript

 

context.fillStyle = "blue";
context.fillRect( 0 , 0 , 100 , 100);
context.globalCompositeOperation = "destination-over";
context.fillStyle = "red";
context.fillRect( 50 , 20 , 100 , 100);
context.globalCompositeOperation = "lighter";
context.fillStyle = "green";
context.fillRect( 70 , 40 , 100 , 100);

 

globalCompositeOperation 描画結果

 

globalCompositeOperationに指定できる値と結果

 

globalCompositeOperationの書式

 

context.globalCompositeOperation = 合成方法;

 

 

合成方法は、あらかじめ定義されている文字列で指定します。

 

source-XXX系

 

sourceはこれから描画する図形を指します。

 

source-で始まる合成方法は、既に描画されている図形の上に重ねる形で、新しい図形を合成処理します。

 

合成後、既存画像・追加画像ともに消去される可能性があります。
消去された部分は、背景色(透明)で描画されています。

 

ブラウザによっては、対応していないケースがあります。
『ブラウザ対応確認』ボタンを押すと、使用中のブラウザが対応しているか確認できます。

 

指定値

意味

結果

source-XXX系
source-over
(デフォルト)
描画した順に上書き

globalCompositeOperation source-over デフォルト

source-atop重なっている部分のみ描画される

globalCompositeOperation source-atop

source-in重なっている部分のみ描画される
元からある図形は消去される

globalCompositeOperation source-in

source-out重なっていない部分のみ描画される
元からある図形は消去される

globalCompositeOperation source-out

 

※青・緑の矩形を初期値で描画した後、globalCompositeOperationに合成方法を指定して、赤い矩形を描画しています。

 

青・緑の矩形を初期値で描画した後、globalCompositeOperationに合成方法を指定して、赤い矩形を描画しています。

 

destination-XXX系

 

destinationは、既に描画されている図形を指します。

 

destination-で始まる合成方法は、既に描画されている図形の下に重ねる形で、新しい図形を合成処理します。

 

合成後、既存画像・追加画像ともに消去される可能性があります。
消去された部分は、背景色(透明)で描画されています。

 

指定値

意味

結果

destination-XXX系
destination-over元からある図形の下に描画

globalCompositeOperation destination-over

destination-atop元からある図形の重なっていない部分が消去される

globalCompositeOperation destination-atop

destination-in元からある図形の重なっている部分のみ描画される

globalCompositeOperationdestination-in

destination-out元からある図形の重なっていない部分のみ描画される

globalCompositeOperation destination-out

 

 

その他系

 

指定値

意味

結果

その他系
lighterカラー値を加算して描画
例:
青00F + 赤F00F0F
緑008000 + 赤F00FF8000

globalCompositeOperation lighter

copy元からある図形を消去して新しい図形を描画

globalCompositeOperation destination-atop

xor元重なっている部分を消去
それ以外は描画

globalCompositeOperation xor

 

 

mix-blend-mode系

 

cssのmix-blend-modeで使用できる値をセットすることができます。

 

mix-blend-modeには、Separable blend modesとNon-separable blend modesの2種類あります。

 

Separable blend modes

 

Separable blend modesは、RGB各色毎に合成処理を行います。

 

指定値

意味

結果

mix-blend-mode系 Separable blend modes
normal
標準的な合成
(そのまま上書き)
をおこないます

globalCompositeOperation normal

multiplyカラー値を乗算します
暗い色になる傾向があります。
例:
青:00F × 赤:F00黒 : 000

globalCompositeOperation multiply

screenカラー値を反転した後乗算し、
その結果を反転します
明るい色になる傾向があります。
例:
青:00F 反転→FF0
赤:F00 反転→0FF
乗算 FF0× 0FF0F0
反転 0F0F0F

globalCompositeOperation screen

overlay既存の図形の色が明るい場合
(カラー値128以上)はscreenで明るく、
暗い場合はmultiplyで暗く描画されます。
※既存の図形の色を
暗いときは明るく、
明るいときは暗くしてから計算するため、
screenまたは
multiply指定時と同じ結果になりません。

globalCompositeOperation overlay

darkenRGB各色で暗い色
(カラー値が小さい)が選択されます

globalCompositeOperation darken

lightenRGB各色で明るい色
(カラー値が大きい)が選択されます

globalCompositeOperation lighten

color-dodge既存の図形の色を、
追加する図形の色を反転させた色で
割った値で描画します。
例:
赤:F00 反転→0FF
青:00F ÷ 0FF001(00F)
緑:008000 ÷ 00FFFF008000

globalCompositeOperation color-dodge

color-burn既存の図形の色を反転させ、
追加する図形の色で割り、
さらに反転させた値で描画します。
例:
青:00F 反転→FF0
FF0 ÷ 赤:F00110(FF0)
(分母0のとき結果を1とする)
FF0 反転→00F

globalCompositeOperation color-burn

hard-light追加するの図形の色が明るい場合
(カラー値128以上)はscreenで明るく、
暗い場合はmultiplyで暗く描画されます。
※追加するの図形の色を
暗いときは明るく、
明るいときは暗くしてから計算するため、
screenまたはmultiply指定時と
同じ結果になりません。

globalCompositeOperation hard-light

soft-lighthard-lightと同様に
追加する図形の色が明るい場合は明るく、
暗い場合は暗くなりますが、
よりソフトに変化します。

globalCompositeOperation soft-light

difference明るい色から暗い色を減算します。
例:
青:00F - 赤:F00F0F
緑:008000 - 赤:FF0000FF8000

globalCompositeOperation difference

 

 

Non-separable blend modes

 

Non-separable blend modesは、RGB個別ではなく、全ての色要素の組み合わせで合成処理をおこないます。

 

指定値

意味

結果

mix-blend-mode系 Non-separable blend modes
hue追加する図形の色相と、
既存の図形の彩度と明度で色を作成します。

globalCompositeOperation hue

saturation既存の図形の色相と明度と、
追加する図形の彩度とで色を作成します。

globalCompositeOperation saturation

color追加する図形の色相と彩度と、
既存の図形の明度でカラーを作成します。

globalCompositeOperation color

luminosity追加する図形の明度と、
既存の図形の色相と彩度で色を作成します。

globalCompositeOperation luminosity

補足:mix-blend-mode合成計算式

 

補足としてmix-blend-modeの合成処理における計算式をお伝えします。

 

計算式は、https://drafts.fxtf.org/compositing/#blendingnonseparableに掲載されているものを、JavaScriptに書き換えています。

 

一部を除き、関数は同じ引数を受け付けます。

 

関数( bc , sc )

 

bc : 既存図形の色要素配列 [ 赤 , 緑 , 青 ] 各 0 ~ 255

 

sc : 追加する図形の色要素配列 [ 赤 , 緑 , 青 ] 各 0 ~ 255

 

Separable blend modes

 

multiply

 

const multiply = ( bc , sc ) => bc.map( ( e , i ) => Math.round(e * sc[i] / 255.0 ) );

 

screen

 

const screen = ( bc , sc ) => bc.map( 
            ( e , i ) =>
                Math.round( ( 1.0 - ( 1.0 - e / 255.0 ) * ( 1.0 - sc[i] / 255.0  )  ) * 255 )
      );

 

overlay

 

const overlay = ( bc , sc ) => bc.map( 
                (e,i) => ( e <= 127) ? multiply( sc[i] , 2.0 * e )
                              :  screen( sc[i] , 2.0 * e - 255.0 ) 
           );

 

darken

 

const darken = ( bc , sc ) => bc.map( ( e , i ) => Math.min( e , sc[i] ) );

 

lighten

 

const lighten = ( bc , sc ) => bc.map( ( e , i ) => Math.max( e , sc[i] ) );

 

color-dodge

 

const colorDodge = ( bc , sc ) =>  bc.map(
              (e,i) => e === 0  ? 0 :
                    (  sc[i] === 255  ? 255 : 
                           Math.min( 255 , Math.round( e / ( 255 - sc[i] ) * 255 ) ) 
                    )
      );

 

color-burn

 

const colorBurn = ( bc , sc ) =>  bc.map( ( e , i ) => 
            255 - ( e === 1  ? 255 : 
                    (  sc[i] === 0  ? 0 : 
                        Math.min( 255 , Math.round( ( 255 - e ) /  sc[i]  * 255 ) )
                    )
                 )
        );

 

hard-light

 

const hardLight = ( bc , sc) => bc.map( ( e ,i ) => 
                ( sc[i] <= 127) ? multiply( e , 2.0 * sc[i] ) :  
                    screen( e , 2.0 * sc[i] - 255.0 )
            );

 

soft-light

 

const softLight = ( bc , sc) => bc.map( ( e , i ) => {
            const [ b , s ] = [ e / 255 , sc[i] / 255 ];
            const res = ( s <= 0.5 ) ?
                      b - ( 1.0 - 2.0 * s ) * b * ( 1.0 - b) :
                      b + ( 2 * s - 1 ) * ( 
                          ( b <= 0.25 ? ( ( 16 * b - 12 ) * b + 4 ) * b :
                               Math.sqrt( b ) ) - b );

 

            return Math.round( res * 255 );
        });

 

difference

 

const difference = ( bc , sc) => bc.map( ( e , i ) => Math.abs( e - sc[i] ) );

 

exclusion

 

const exclusion = ( bc , sc) => bc.map( ( e , i ) => 
         Math.round( e + sc[i] - 2 *  e *  sc[i] / 255) 
   );

 

Non-separable blend modes

 

共通関数 1

 

Non-separable blend modes処理で、共通的に使用している関数です。
引数は独自のものをとります。

 

const Lum = C => 0.3 * C.red + 0.59 * C.green + 0.11 * C.blue;

 

const ClipColor = C => {
            const L = Lum(C);
            const n = Math.min( C.red, C.green, C.blue );
            const x = Math.max( C.red, C.green, C.blue );

 

            const f1 = cl => L + (((cl - L) * L) / (L - n));
            const f2 = cl => L + (((cl - L) * (1 - L)) / (x - L));

 

            const res = { ...C };

 

            if( n < 0)
                [ res.red , res.green , res.blue ] = 
                           [ f1( res.red ) , f1( res.green ) , f1( res.blue ) ];

 

            if( x > 1)
                [ res.red , res.green , res.blue ] = 
                           [ f2( res.red ) , f2( res.green ) , f2( res.blue ) ];

 

            return res;
};

 

const SetLum = ( C ,  l ) =>{
                const d = l - Lum( C );
                const res = {};
                [ res.red , res.green , res.blue ] = 
                         [  C.red + d , C.green + d ,  C.blue + d ];

 

                return ClipColor( res )
};

 

const Sat = C => Math.max( C.red, C.green, C.blue ) 
                              - Math.min( C.red, C.green, C.blue );

 

const SetSat = (C, s) => {
            let sobj = [
                { name : "red", val : C.red },
                { name : "green", val : C.green },
                { name : "blue", val : C.blue }
            ];
            sobj = sobj.sort( ( a , b ) => b.val - a.val );

 

            const rObj = {};

 

            if( sobj[0].val > sobj[2].val ) {
                rObj.Cmid = (((sobj[1].val - sobj[2].val) * s) / (sobj[0].val - sobj[2].val))
                rObj.Cmax = s;
            } else {
                rObj.Cmid = rObj.Cmax = 0;
            }
            rObj.Cmin = 0;

 

            const res = {};
            res[ sobj[0].name ] = rObj.Cmax;
            res[ sobj[1].name ] = rObj.Cmid;
            res[ sobj[2].name ] = rObj.Cmin;
            return res;
};

 

共通関数 2

 

各関数で受け取った色配列を、共通関数 1で使用するオブジェクトに変換、または逆変換をおこなうオリジナル関数です。

 

    // カラー値を0.0~1.0の範囲に変換し、オブジェクトにセット
const setColorObj = c => ({ 
      red : c[0] / 255 , 
      green : c[1] / 255 , 
      blue : c[2] / 255 
});

 

    // カラー値を0~255の範囲に変換し、配列にセット
const restoreColorObj = c => [ 
     Math.round( c.red * 255 ) , 
     Math.round( c.green * 255 ) , 
     Math.round( c.blue * 255 ) 
];

 

hue

 

const hue = ( bc , sc ) => {
            const [ bcc , scc ] = [ setColorObj(bc) , setColorObj(sc) ];
            return restoreColorObj( SetLum(SetSat(scc, Sat(bcc)), Lum(bcc)) );
        };

 

saturation

 

const saturation = ( bc , sc ) => {
            const [ bcc , scc ] = [ setColorObj(bc) , setColorObj(sc) ];
            return restoreColorObj( SetLum(SetSat(bcc, Sat(scc)), Lum(bcc)) );
        };

 

color

 

const color = ( bc , sc ) => {
            const [ bcc , scc ] = [ setColorObj(bc) , setColorObj(sc) ];
            return restoreColorObj( SetLum( scc , Lum(bcc)) );
        };

 

luminosity

 

const luminosity = ( bc , sc ) => {
            const [ bcc , scc ] = [ setColorObj(bc) , setColorObj(sc) ];
            return restoreColorObj( SetLum( bcc , Lum(scc)) );
        };

スポンサーリンク

記事の内容について

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

OFUSEを設置してみました。
応援いただけると嬉しいです。