Source: dev/offScreenTypeC.js

/**
 * offScreenクラス
 * (offscreen buffer)
 * @class DisplayControl.offScreenTypeC
 * @classdesc
 * DisplayControlクラスの内部で利用されるオフスクリーンバッファを管理します<br>\
 * 実際の描画はここで行われ、その後メインCanvasにまとめて転送されることで、<br>\
 * 描画パフォーマンスと複雑なグラフィック効果を実現します。<br>\
 * <br>\
 * //全画面表示する為、mainのCanvasにまとめて重ね表示
 */
class offScreenTypeC {
    /**
     * @param {number} w 作成サイズ幅指定
     * @param {number} h 作成サイズ高さ指定
     * @param {number} ix 水平方向オフセット 
     * @param {number} iy 垂直方向オフセット  
     * @description
     * offScreenTypeCインスタンスを初期化し、指定された幅と高さでオフスクリーンCanvasを作成します。<br>\
     * 2D描画コンテキストを取得し、オフセットや描画フラグなどの内部状態を設定します。
     */
    constructor(w, h, ix, iy) {
        //w : width, h:height
        let element = new OffscreenCanvas(w, h);//2DEFで更新する場合があるのでconstはNG

        const offset_x = ix;
        const offset_y = iy;

        let efcnt = 0; //CallFunctionCount
        let efmax = 0; //CallFunctionCount(Max)

        let device = element.getContext("2d");//2DEFで更新有

        let enable_draw_flag = true;
        let enable_reset_flag = true;

        let _2DEffectEnable = false; //default off
        let view_angle = 0;

        //[Mode Functions]
        /**
         * MODE CHANGE ENNABLE_DRAW_FLAG
         * @method
         * @param {boolean} [flg=null] enable_draw_flag
         * @returns {boolean} 現在値
         * @todo 現在は効果なし/使用箇所確認後、削除予定
         * @description
         * オフスクリーンバッファをメインCanvasに描画するかどうかを制御します。<br>\
         * `true`を設定すると描画が有効になり、`false`で無効になりますが、<br>\
         * 現在の実装では効果が限定的である可能性があります。
         */
        this.view = function (flg) {
            if (typeof flg == "boolean") {
                enable_draw_flag = flg;
            }
            return enable_draw_flag;
        };
        /**
         * MODE CHANGE ENNABLE_FLIP_FLAG
         * @method
         * @param {boolean} [flg=null] enable_flip_flag
         * @returns {boolean} 現在値
         * @todo 現在は効果なし/使用箇所確認後、削除予定
         * @description
         * オフスクリーンバッファが自動的にクリアされるかどうかを制御します。<br>\
         * `true`を設定するとクリアが有効になり、`false`で無効になりますが、<br>\
         * 現在の実装では効果が限定的である可能性があります。
         */
        this.flip = function (flg) {
            if (typeof flg == "boolean") {
                enable_reset_flag = flg;
            }
            return enable_draw_flag;
        };
        /**
         * FULLSCREEN ROTATE FUNCTION
         * @method
         * @param {numver} r rotate angle
         * @desc 
         * this function effect eneble :_2DEffectEnable:true<br><br>\
         * <br>\
         * フルスクリーン2Dエフェクトが有効な場合、オフスクリーンバッファ全体を回転させます。<br>\
         * 指定された角度でバッファの内容が変換され、<br>\
         * 画面全体に回転効果を適用します。
         */
        this.turn = function (r) {
            if (_2DEffectEnable)
                view_angle = r;
        };
        /**
         * 2D FULLSCREEN EFFECT FUNCTION ENABLE
         * @method
         * @param {boolean} f ENABLE FLAG
         * @description
         * フルスクリーン2Dエフェクトの有効/無効を切り替えます。<br>\
         * 有効にした場合、回転時の枠外乱れを防ぐためバッファサイズを2倍に拡張し、<br>\
         * 描画原点を中心に移動させます
         * @todo 縦横2倍ではなく縦横を長辺の2倍にしないと足りない<br>\
         * 回転機能をあらためて使う案件が出てきたら補正する
         */
        this._2DEF = function (f) {
            _2DEffectEnable = f;

            if (f) {
                //回転で枠外が乱れるのでBackbufferを縦横2倍にする
                element = new OffscreenCanvas(w * 2, h * 2);
                //書き込み位置の原点(0,0)を中心近くに寄せる
                device = element.getContext("2d");
                device.translate(w / 2, h / 2);
            } else {
                element = new OffscreenCanvas(w, h);
                device = element.getContext("2d");
                device.translate(0, 0);
            }
        };
        //[Draw Functions]
        //-------------------------------------------------------------
        //SP_PUT
        /**
         * 変形ありの画像出力(SpritePut)(背景回転)FullParameter
         * @method
         * @param {Img} img 画像データ
         * @param {number} sx source x 元画像での位置x
         * @param {number} sy source y 元画像での位置y
         * @param {number} sw source w 元画像の幅
         * @param {number} sh source h 元画像の高さ
         * @param {number} dx destination x 出力画像の位置x
         * @param {number} dy destination y 出力画像の位置Y
         * @param {number} dw destination w  出力画像の幅
         * @param {number} dh destination h 出力画像の高さ
         * @param {number} m11 transform param
         * @param {number} m12 transform param
         * @param {number} m21 transform param
         * @param {number} m22 transform param
         * @param {number} tx target x 変形時の出力先x
         * @param {number} ty target y 変型時の出力先y
         * @param {number} alpha alpha 透明度指定(0-255)/不透明255
         * @param {number} r radian 方向上を基準0にした回転方向(0-359)
         * @returns {void}
         * @description
         * 画像を変形(回転、反転、拡大・縮小)させながら描画します。<br>\
         * 元画像の切り出し範囲、表示位置、変形パラメータ、アルファ値、回転角を細かく指定し、<br>\
         * 複雑なスプライト描画を可能にします。
         */
        this.spPut = function (img, sx, sy, sw, sh, dx, dy, dw, dh, m11, m12, m21, m22, tx, ty, alpha, r) {

            device.save();
            if (_2DEffectEnable) { tx += w / 2; ty += h / 2; };
            device.setTransform(m11, m12, m21, m22, tx, ty);
            if (r != 0) { device.rotate(Math.PI / 180 * r); }

            if (alpha == 255) {
                device.globalCompositeOperation = "source-over";
            } else {
                //if (this.light_enable) device.globalCompositeOperation = "lighter"; //source-over
                device.globalAlpha = alpha * (1.0 / 255);
            }
            //if (_2DEffectEnable){device.translate(w/2,h/2);};
            device.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);

            device.restore();
            //device.setTransform( 1,0,0,1,0,0 );
            efcnt++;
        };
        //-------------------------------------------------------------
        //DRAWIMG_XYWH_XYWH
        /**
         * 画像出力(サイズ変更有)
         * @method
         * @param {Img} img 画像データ
         * @param {number} sx source x 元画像での位置x
         * @param {number} sy source y 元画像での位置y
         * @param {number} sw source w 元画像の幅
         * @param {number} sh source h 元画像の高さ
         * @param {number} dx destination x 出力画像の位置x
         * @param {number} dy destination y 出力画像の位置Y
         * @param {number} dw destination w  出力画像の幅
         * @param {number} dh destination h 出力画像の高さ
         * @returns {void}
         * @description
         * 画像の一部を切り出して、指定された位置とサイズで描画します<br>\
         * 元画像(source)のX, Y, 幅, 高さ、<br>\
         * そして描画先(destination)のX, Y, 幅, 高さを指定します。
         */
        this.drawImgXYWHXYWH = function (img, sx, sy, sw, sh, dx, dy, dw, dh) {

            device.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);

            efcnt++;
        };
        //-------------------------------------------------------------
        //FILLTEXT
        /**
         * 文字表示
         * @method
         * @param {string} str 表示文字列 
         * @param {number} x 表示座標x
         * @param {number} y 表示座標y
         * @param {Color} c 表示色 (省略の場合"limegreen")
         * @description
         * 指定された文字列をオフスクリーンバッファに描画します。<br>\
         * 文字列、X座標、Y座標、そして表示色をパラメータとして受け取り、<br>\
         * バッファの描画コンテキストでテキストを描画します。
         */
        this.fillText = function (str, x, y, c) {

            if (!Boolean(c)) { c = "limegreen"; }

            device.fillStyle = c;
            device.fillText(str, x, y);

            efcnt++;
        };
        //------------------------------------------------------------
        //DRAWIMG_XY
        /**
         * 画像出力(元画像そのまま)
         * @method
         * @param {Img} img 画像データ
         * @param {number} sx 表示位置x
         * @param {number} sy 表示位置y
         * @returns {void}
         * @description
         * 画像全体を元のサイズそのままに、指定された位置に描画します。<br>\
         * 画像データと表示位置のX, Y座標を指定する、<br>\
         * 最もシンプルな画像描画メソッドです。
         */
        this.drawImgXY = function (img, sx, sy) {

            device.drawImage(img, sx, sy);

            efcnt++;
        };
        //------------------------------------------------------------
        //DRAWIMG_XYWH
        /**
         * 画像出力(元画像全体をサイズ変更)
         * @method 
         * @param {Img} img 画像データ
         * @param {number} sx source x 表示位置x
         * @param {number} sy source y 表示位置y
         * @param {number} sw source w 幅
         * @param {number} sh source h 高さ
         * @returns {void}
         * @description
         * 画像全体を、指定された幅と高さに拡大・縮小して描画します。<br>\
         * 画像データ、表示位置のX, Y座標、そして描画したい幅と高さを指定し<br>\
         * 画像サイズを調整して表示します。
         */ 
        this.drawImgXYWH = function (img, sx, sy, sw, sh) {

            device.drawImage(img, sx, sy, sw, sh);

            efcnt++;
        };
        //------------------------------------------------------------
        //PUTIMAGETRANSFORM
        /**
         * 画像出力(元画像全体を変形して表示)
         * @method 
         * @param {Img} img 画像データ
         * @param {number} x 表示位置x
         * @param {number} y 表示位置y
         * @param {number} m11 transform param
         * @param {number} m12 transform param
         * @param {number} m21 transform param
         * @param {number} m22 transform param
         * @returns {void}
         * @description
         * 画像全体に変形行列を適用して描画します。<br>\
         * 画像データ、表示位置X, Y、そして変換行列のパラメータを指定し<br>\
         * 画像の回転、拡大・縮小、せん断などをまとめて適用できます。
         */
        //use img, m11, m12, m21, m22, tx, ty
        //------------------------------------------------------------
        this.putImageTransform = function (img, x, y, m11, m12, m21, m22) {

            device.save();

            device.setTransform(m11, m12, m21, m22, x, y);
            device.drawImage(img, 0, 0);

            device.restore();

            efcnt++;
        };
        //---------------------------------------------------------
        //TRANSFORM
        /**
         * 変形(emptyFunction)
         * @method 
         * @param {number} m11 transform param
         * @param {number} m12 transform param
         * @param {number} m21 transform param
         * @param {number} m22 transform param
         * @returns {void}
         * @todo 削除予定
         * @deprecaed
         * @description
         * オフスクリーンバッファの描画コンテキストに変形行列を適用します。<br>\
         * 現在は機能しないダミー関数です。
         */
        this.transform = function (m11, m12, m21, m22) {
            //dummy
            efcnt++;
        };
        //------------------------------------------------------------
        // PUTFUNC
        /**
         * CustomDrawObject
         * @typedef {object} PutFuncCustomDraw draw(device)を含むオブジェクト
         * @property {function} draw 必須 {DeviceContext}を引数に呼び出される
         * @property {*} any 任意のプロパティ 
         * @summary CanvasMethodを登録して表示させる。
         * device: {DeviceContext} 
         * @example
         * cl = { x: 100, y:100, r:30, c:"red",
         *      draw:(d)=>{
         *          d.beginPath();
         *          d.strokeStyle = this.c;
         *          d.lineWidth = 1;
         *          d.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
         *          d.stroke();
         *          }
         *      }
         */

        /**
         * カスタム描画表示(CanvasMethodを実行)
         * @method 
         * @param {PutFuncCustomDraw} cl draw(device)を含むオブジェクト
         * @returns {void}
         * @description
         * `draw(device)`メソッドを持つカスタム描画オブジェクトを登録し、実行します。<br>\
         * この機能により、開発者はCanvasの低レベルな描画APIを直接利用して<br>\
         * グラフィック処理をオフスクリーンバッファ上で行うことができます。
         */
        this.putFunc = function (cl) {

            cl.draw(device);
            //ここで登録するクラスには表示の為に"draw( device )" functionを必ず登録
            efcnt++;
        };

        //---------------------------------------------------------
        //ALLCLEAR
        /**
         * 指定範囲の消去(クリア)
         * @method 
         * @param {number} sx 指定位置x
         * @param {number} sy 指定位置y
         * @param {number} sw 幅
         * @param {number} sh 高さ
         * @returns {void}
         * @description
         * オフスクリーンバッファの指定された矩形範囲を完全に消去します。<br>\
         * X, Y座標、幅, 高さを指定し、<br>\
         * 既存の描画内容をクリアします。
         */
        this.allClear = function (sx, sy, sw, sh) {

            device.save();

            device.setTransform(1, 0, 0, 1, 0, 0);
            device.clearRect(sx, sy, sw, sh);

            device.restore();

            efcnt++;
        };

        //-----------------------------------------------------
        //FILLRECT
        /**
         * 指定範囲の消去(クリア)
         * @method 
         * @param {number} sx 指定位置x
         * @param {number} sy 指定位置y
         * @param {number} sw 幅
         * @param {number} sh 高さ
         * @param {string} color 塗り潰し色(省略で透明色) 
         * @returns {void}
         * @description
         * オフスクリーンバッファの指定された矩形範囲を色で塗りつぶします。<br>\
         * X, Y座標、幅, 高さ、そして塗りつぶし色を指定し、<br>\
         * 色を指定しない場合はその範囲をクリアします。
        */
        this.fillRect = function (sx, sy, sw, sh, color) {

            if (Boolean(color)) {
                device.fillStyle = color;
                device.fillRect(sx, sy, sw, sh);
            } else {
                device.clearRect(sx, sy, sw, sh);
            }

            efcnt++;
        };

        //----------------------------------------------------------
        //RESET
        // 
        /**
         * offScreenバッファのクリア
         * @method 
         * @returns {void}
         * @description
         * オフスクリーンバッファ全体をクリアします。<br>\
         * `enable_reset_flag`が`true`の場合にのみ実行され、<br>\
         * バッファの内容を初期状態に戻します。
         */
        this.reset = function () {

            if (enable_reset_flag) {
                this.allClear(0, 0, w, h);
            }

            efcnt++;
        };

        //----------------------------------------------------------
        //REFLASH
        /**
         * (flameloopで実行用)offScreenバッファのクリア
         * @method 
         * @returns {void}
         * @description
         * ゲームのフレームループ内で呼び出されることを想定した<br>\
         * オフスクリーンバッファのクリア機能です。<br>\
         * `enable_reset_flag`が`true`であれば、`reset`メソッドを呼び出します。
         */
        this.reflash = function () {

            if (enable_reset_flag) {
                this.reset();
            }
            efcnt++;
        };

        //----------------------------------------------------------
        //DRAW
        /**
         * 描画処理
         * OffscreenCanvasをCanvasへ反映
         * @method 
         * @param {CanvasContext} outdev 出力先のCanvas2DContext(MainCanvas)
         * @returns {void}
         * @description
         * オフスクリーンバッファに描画された内容を、出力先のメインCanvasに転送します。<br>\
         * 2Dエフェクトが有効な場合は、回転などの効果を適用しながら<br>\
         * メインCanvasに反映させます。
         */
        this.draw = function (outdev) {
            //2024/04/29 new Function turn
            if (enable_draw_flag) {
                if (!_2DEffectEnable) {
                    //outdev.clearRect(0, 0, w, h);
                    outdev.drawImage(element, offset_x, offset_y);
                } else {
                    let w = element.width;
                    let h = element.height;

                    outdev.fillStyle = "green";
                    outdev.fillRect(0, 0, w / 2, h / 2);

                    //outdev.clearRect(0, 0, w/2, h/2);
                    outdev.save();
                    outdev.translate(w / 4, h / 4);
                    outdev.rotate((Math.PI / 180) * ((view_angle) % 360));

                    outdev.drawImage(element, offset_x - w / 2, offset_y - h / 2);
                    //outdev.drawImage(element, offset_x, offset_y);
                    outdev.restore();
                    //console.log("e" + view_angle%360);
                    device.fillStyle = "red";
                    device.fillRect(-w / 4, -h / 4, w, h);
                }
            }
            if (efmax < efcnt) efmax = efcnt;
            efcnt = 0; //Drawコール毎の呼び出し数の記録の為、メインキャンバスに反映毎に0にする
        };
        //----------------------------------------------------------
        //COUNT
        /**
         * 前回のDrawコールから現在までのFunction呼び出し回数を返す
         * @returns {number} function call count par frame
         * @description
         * 前回の`draw`メソッドが呼び出されてから現在までに、<br>\
         * オフスクリーンバッファに対して行われた描画関数の呼び出し回数を返します。<br>\
         * これにより、1フレームあたりの描画操作の数を把握できます。
         */
        this.count = function () {
            //return function call count par frame
            return efcnt;
        };
        //----------------------------------------------------------
        //MAX
        /**
         * Function呼び出し回数の最大値を返す
         * @returns {number} function call count par frame
         * @description
         * オフスクリーンバッファへの描画関数呼び出し回数の最大値を返します。<br>\
         * これは、フレーム間で最も多くの描画操作が行われた際の記録であり<br>\
         * 描画負荷のピークを把握するのに役立ちます。
         */
        this.max = function () {
            //return function call count par frame maxim
            return efmax;
        };
    }
}