このページは、【JavaScript】 自力でCanvas画像をBMPに変換する
『パターン4:PNG/JPEGでデータ部を生成』のデモページです



ソースコード

HTML

<html lang="ja">
<head>
<meta charset="UTF-8">
<script src="./bitmap-ui.js" type="module"></script>
<style>
    #inch{
      width: 1in;
      height: 1in;
      padding: 0;
      border: 0;
      box-sizing: border-box;
    }
</style>
</head>
<body>
    <div id="inch"></div>
    <p><input type="file" id="file_select"></p>
    <p><label><input type="radio" name="type" value="PNG" checked>PNG</label>
    <label><input type="radio" name="type" value="JPEG" >JPEG</label></p>
    <p><button id="button">bmp生成</button></p>
    <p><canvas id="canvas"></canvas></p>
</body>
</html>

bitmap-ui.js

import {imageDataToBmp}  from "./imagedatatobmp.js";
(()=>{

    window.addEventListener( "DOMContentLoaded" , ()=>{
        // dpi取得
        const dpi = document.getElementById("inch").offsetHeight;
        start("file_select","canvas","button","type",dpi);
    });

    const start = (fileSelectId,canvasId,buttonId,typeName,dpm) =>{

        const context = document.getElementById(canvasId).getContext("2d");
        const type = document.getElementsByName(typeName);
        const getType = ()=>type[ Array.prototype.findIndex.call(type, e=>e.checked ) ].value;

        const image = new Image();
        image.onload = ()=>{
            context.canvas.width = image.width;
            context.canvas.height = image.height;
            context.drawImage(image, 0, 0, context.canvas.width, context.canvas.height);
        };
        image.onerror = ()=>alert("読み込めませんでした");

        const reader = new FileReader();
        reader.onload = () => image.src = reader.result

        document.getElementById(fileSelectId)
            .addEventListener("change",function(){
                reader.readAsDataURL(this.files[0]);
            });

        document.getElementById(buttonId)
            .addEventListener("click",()=>bitmapDownload(context,getType(),dpm));
    };
    // ビットマップファイルダウンロード
    const bitmapDownload = (context,type,dpi) =>{
        const {width,height} = context.canvas;

        imageDataToBmp( context.canvas , width , height ,type , dpi )
            .then( buffers => {
                const blob = new Blob( buffers, { type:  "octet/stream" });
                const link = document.createElement("a");
                link.href = URL.createObjectURL(blob);
                link.download = "image.bmp";
                link.click();

                URL.revokeObjectURL(link.href);

            });
    };

})();

imagedatatobmp.js

export {imageDataToBmp};
const imageDataToBmp = (()=>{
    "use strict";

    // ヘッダー生成用フォーマット定数
    const [U8,U16,U32,I32] = (()=>{
        const dp = DataView.prototype;
        return [[dp.setUint8,1],[dp.setUint16,2],[dp.setUint32,4],[dp.setInt32,4]];
    })();
    // ヘッダー生成
    const getHeader = (format,values)=> {
        const size = format.reduce((a,b)=>a+b[1],0);
        const buffer = new DataView(new ArrayBuffer( size ));
        values.reduce((a,b,index)=>{
            format[index][0].call(buffer,a,b,true);
            return a + format[index][1];
        },0);
        return buffer;
    };

    const getImageBuffer = (canvas,type) =>{
        const types = { "PNG":"image/png" , "JPEG" : "image/jpeg" };
        return  new Promise( resolve=>{
            canvas.toBlob( blob =>resolve(blob.arrayBuffer()),types[type],0.7);
        });
    }

    // BMPバイナリイメージの生成
    return  (canvas,width,height,type,dpi=96) => {
        const dpm = Math.ceil((dpi === 0 ? 96 : dpi) * 39.3701 );

        return  getImageBuffer(canvas,type)
            .then( imgBuffer =>{
                // BITMAPFILEHEADERのセット
                const dataOffset = 14 + 40;
                const fileSize = imgBuffer.byteLength + dataOffset;

                const fileHeader =  getHeader( [U8,U8,U32,U16,U16,U32]
                    ,["B".charCodeAt(0) , "M".charCodeAt(0) , fileSize , 0 , 0 ,dataOffset]);

                const BI_TYPE = { "PNG":5 , "JPEG" : 4 };
                const infoHeader =  getHeader( [U32,U32,I32,U16,U16,U32,U32,U32,U32,U32,U32]
                    ,[40 , width , height , 1 , 0
                        ,BI_TYPE[type] , imgBuffer.byteLength , dpm , dpm , 0 , 0]);

                return [fileHeader.buffer,infoHeader.buffer,imgBuffer];
            });
    };

})();