孝感市临终服务网

使用JavaScript将PNG格式图片转为BMP格式的完整实现代码

2026-03-30 23:17:01 浏览次数:0
详细信息

方案一:使用Canvas API(浏览器环境)

<!DOCTYPE html>
<html>
<head>
    <title>PNG转BMP</title>
</head>
<body>
    <input type="file" id="pngInput" accept=".png">
    <button id="convertBtn">转换为BMP</button>
    <a id="downloadLink" style="display:none">下载BMP</a>

    <script>
        class PNGtoBMPConverter {
            constructor() {
                this.pngInput = document.getElementById('pngInput');
                this.convertBtn = document.getElementById('convertBtn');
                this.downloadLink = document.getElementById('downloadLink');

                this.attachEvents();
            }

            attachEvents() {
                this.convertBtn.addEventListener('click', () => this.convertPNGtoBMP());
            }

            async convertPNGtoBMP() {
                if (!this.pngInput.files.length) {
                    alert('请先选择PNG文件');
                    return;
                }

                const file = this.pngInput.files[0];

                // 验证文件类型
                if (!file.type.includes('png')) {
                    alert('请选择PNG格式图片');
                    return;
                }

                try {
                    // 读取PNG文件
                    const arrayBuffer = await this.readFileAsArrayBuffer(file);
                    const bmpData = await this.convertToBMP(arrayBuffer);

                    // 创建下载链接
                    this.createDownloadLink(bmpData, file.name.replace('.png', '.bmp'));

                } catch (error) {
                    console.error('转换失败:', error);
                    alert('转换失败: ' + error.message);
                }
            }

            readFileAsArrayBuffer(file) {
                return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onload = (e) => resolve(e.target.result);
                    reader.onerror = (e) => reject(new Error('文件读取失败'));
                    reader.readAsArrayBuffer(file);
                });
            }

            async convertToBMP(arrayBuffer) {
                return new Promise((resolve, reject) => {
                    // 创建Blob并生成图片URL
                    const blob = new Blob([arrayBuffer], { type: 'image/png' });
                    const url = URL.createObjectURL(blob);
                    const img = new Image();

                    img.onload = () => {
                        try {
                            // 创建Canvas
                            const canvas = document.createElement('canvas');
                            canvas.width = img.width;
                            canvas.height = img.height;

                            const ctx = canvas.getContext('2d');

                            // 绘制图片到Canvas
                            ctx.drawImage(img, 0, 0);

                            // 获取图像数据并转换为BMP
                            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                            const bmpBuffer = this.imageDataToBMP(imageData);

                            // 清理URL
                            URL.revokeObjectURL(url);

                            resolve(bmpBuffer);
                        } catch (error) {
                            reject(error);
                        }
                    };

                    img.onerror = () => {
                        URL.revokeObjectURL(url);
                        reject(new Error('图片加载失败'));
                    };

                    img.src = url;
                });
            }

            /**
             * 将ImageData转换为BMP格式
             * @param {ImageData} imageData - Canvas的图像数据
             * @returns {ArrayBuffer} BMP文件数据
             */
            imageDataToBMP(imageData) {
                const width = imageData.width;
                const height = imageData.height;
                const data = imageData.data;

                // BMP文件结构
                const FILE_HEADER_SIZE = 14;
                const INFO_HEADER_SIZE = 40;
                const PIXEL_DATA_OFFSET = FILE_HEADER_SIZE + INFO_HEADER_SIZE;

                // 每行像素数据的字节数(需要4字节对齐)
                const rowSize = Math.floor((width * 3 + 3) / 4) * 4;
                const pixelDataSize = rowSize * height;
                const fileSize = PIXEL_DATA_OFFSET + pixelDataSize;

                // 创建ArrayBuffer
                const buffer = new ArrayBuffer(fileSize);
                const view = new DataView(buffer);

                // BMP文件头
                view.setUint8(0, 0x42);  // 'B'
                view.setUint8(1, 0x4D);  // 'M'
                view.setUint32(2, fileSize, true);  // 文件大小
                view.setUint32(6, 0, true);  // 保留字段
                view.setUint32(10, PIXEL_DATA_OFFSET, true);  // 像素数据偏移

                // BMP信息头
                view.setUint32(14, INFO_HEADER_SIZE, true);  // 信息头大小
                view.setInt32(18, width, true);  // 宽度
                view.setInt32(22, height, true);  // 高度
                view.setUint16(26, 1, true);  // 颜色平面数
                view.setUint16(28, 24, true);  // 每像素位数(24位BMP)
                view.setUint32(30, 0, true);  // 压缩方式(无压缩)
                view.setUint32(34, pixelDataSize, true);  // 像素数据大小
                view.setInt32(38, 2835, true);  // 水平分辨率
                view.setInt32(42, 2835, true);  // 垂直分辨率
                view.setUint32(46, 0, true);  // 使用的颜色数
                view.setUint32(50, 0, true);  // 重要颜色数

                // 写入像素数据(BMP是BGR格式,且从下往上存储)
                for (let y = 0; y < height; y++) {
                    for (let x = 0; x < width; x++) {
                        const srcIndex = (y * width + x) * 4;
                        const dstIndex = PIXEL_DATA_OFFSET + ((height - 1 - y) * rowSize + x * 3);

                        // 从RGBA转换为BGR(忽略Alpha通道)
                        view.setUint8(dstIndex + 0, data[srcIndex + 2]);  // Blue
                        view.setUint8(dstIndex + 1, data[srcIndex + 1]);  // Green
                        view.setUint8(dstIndex + 2, data[srcIndex + 0]);  // Red
                    }
                }

                return buffer;
            }

            createDownloadLink(bmpBuffer, fileName) {
                // 创建Blob
                const blob = new Blob([bmpBuffer], { type: 'image/bmp' });
                const url = URL.createObjectURL(blob);

                // 设置下载链接
                this.downloadLink.href = url;
                this.downloadLink.download = fileName;
                this.downloadLink.style.display = 'inline';
                this.downloadLink.textContent = `下载 ${fileName}`;

                // 自动点击下载
                this.downloadLink.click();
            }
        }

        // 初始化转换器
        new PNGtoBMPConverter();
    </script>
</body>
</html>

方案二:使用Node.js(服务器端)

const fs = require('fs');
const path = require('path');
const { createCanvas, loadImage } = require('canvas');

class PNGtoBMPConverterNode {
    constructor() {
        // BMP文件头常量
        this.BMP_HEADER_SIZE = 14;
        this.BMP_INFO_SIZE = 40;
        this.BMP_PIXEL_OFFSET = 54; // 14 + 40
    }

    /**
     * 将PNG文件转换为BMP文件
     * @param {string} pngPath - PNG文件路径
     * @param {string} bmpPath - BMP输出路径(可选)
     * @returns {Promise<string>} 输出文件路径
     */
    async convertFile(pngPath, bmpPath = null) {
        try {
            // 生成输出路径
            if (!bmpPath) {
                const ext = path.extname(pngPath);
                bmpPath = pngPath.replace(ext, '.bmp');
            }

            // 加载PNG图片
            const image = await loadImage(pngPath);

            // 创建Canvas
            const canvas = createCanvas(image.width, image.height);
            const ctx = canvas.getContext('2d');

            // 绘制图片
            ctx.drawImage(image, 0, 0);

            // 获取图像数据
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

            // 转换为BMP格式
            const bmpBuffer = this.imageDataToBMPBuffer(imageData);

            // 写入文件
            fs.writeFileSync(bmpPath, bmpBuffer);

            console.log(`转换成功: ${pngPath} -> ${bmpPath}`);
            return bmpPath;

        } catch (error) {
            console.error('转换失败:', error);
            throw error;
        }
    }

    /**
     * 将ImageData转换为BMP Buffer
     * @param {ImageData} imageData - 图像数据
     * @returns {Buffer} BMP文件Buffer
     */
    imageDataToBMPBuffer(imageData) {
        const width = imageData.width;
        const height = imageData.height;
        const data = imageData.data;

        // 计算行大小(4字节对齐)
        const rowSize = Math.floor((width * 3 + 3) / 4) * 4;
        const pixelDataSize = rowSize * height;
        const fileSize = this.BMP_PIXEL_OFFSET + pixelDataSize;

        // 创建Buffer
        const buffer = Buffer.alloc(fileSize);

        // BMP文件头
        buffer.write('BM', 0);  // 文件类型
        buffer.writeUInt32LE(fileSize, 2);  // 文件大小
        buffer.writeUInt32LE(0, 6);  // 保留字段
        buffer.writeUInt32LE(this.BMP_PIXEL_OFFSET, 10);  // 像素数据偏移

        // BMP信息头
        buffer.writeUInt32LE(this.BMP_INFO_SIZE, 14);  // 信息头大小
        buffer.writeInt32LE(width, 18);  // 宽度
        buffer.writeInt32LE(height, 22);  // 高度
        buffer.writeUInt16LE(1, 26);  // 颜色平面数
        buffer.writeUInt16LE(24, 28);  // 每像素位数
        buffer.writeUInt32LE(0, 30);  // 压缩方式
        buffer.writeUInt32LE(pixelDataSize, 34);  // 像素数据大小
        buffer.writeInt32LE(2835, 38);  // 水平分辨率
        buffer.writeInt32LE(2835, 42);  // 垂直分辨率
        buffer.writeUInt32LE(0, 46);  // 使用的颜色数
        buffer.writeUInt32LE(0, 50);  // 重要颜色数

        // 写入像素数据(BGR格式,从下往上)
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const srcIndex = (y * width + x) * 4;
                const dstIndex = this.BMP_PIXEL_OFFSET + ((height - 1 - y) * rowSize + x * 3);

                // RGBA转BGR(忽略Alpha)
                buffer[dstIndex] = data[srcIndex + 2];  // Blue
                buffer[dstIndex + 1] = data[srcIndex + 1];  // Green
                buffer[dstIndex + 2] = data[srcIndex];  // Red
            }
        }

        return buffer;
    }

    /**
     * 批量转换PNG文件
     * @param {string[]} pngPaths - PNG文件路径数组
     * @param {string} outputDir - 输出目录
     */
    async convertBatch(pngPaths, outputDir) {
        const results = [];

        for (const pngPath of pngPaths) {
            try {
                const filename = path.basename(pngPath, '.png');
                const outputPath = path.join(outputDir, `${filename}.bmp`);

                const result = await this.convertFile(pngPath, outputPath);
                results.push({ input: pngPath, output: result, success: true });
            } catch (error) {
                results.push({ input: pngPath, error: error.message, success: false });
            }
        }

        return results;
    }
}

// 使用示例
async function main() {
    const converter = new PNGtoBMPConverterNode();

    // 转换单个文件
    await converter.convertFile('input.png', 'output.bmp');

    // 批量转换
    const results = await converter.convertBatch(
        ['image1.png', 'image2.png'],
        './bmp_output'
    );

    console.log('批量转换结果:', results);
}

// 安装依赖:npm install canvas
// 运行:node png-to-bmp.js

module.exports = PNGtoBMPConverterNode;

方案三:纯JavaScript实现(不依赖Canvas)

// 仅处理24位非压缩BMP
class PNGtoBMPPureJS {
    /**
     * 使用纯JavaScript将PNG转换为BMP
     * 注意:这种方法对PNG解析有限制,仅适用于简单的PNG文件
     */
    static async convertPNGtoBMP(pngArrayBuffer) {
        // 解析PNG数据
        const pngData = this.parsePNG(pngArrayBuffer);

        // 转换为BMP格式
        return this.createBMPFromPNGData(pngData);
    }

    static parsePNG(arrayBuffer) {
        const dataView = new DataView(arrayBuffer);
        const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10];

        // 验证PNG签名
        for (let i = 0; i < 8; i++) {
            if (dataView.getUint8(i) !== pngSignature[i]) {
                throw new Error('无效的PNG文件');
            }
        }

        // 简化的PNG解析(实际应用中建议使用库如png.js)
        // 这里仅演示基本结构
        return {
            width: dataView.getUint32(16),
            height: dataView.getUint32(20),
            colorType: dataView.getUint8(25),
            bitDepth: dataView.getUint8(24),
            // 注意:实际PNG解析更复杂,需要处理多个数据块
        };
    }

    static createBMPFromPNGData(pngData) {
        // 创建简单BMP(单色示例)
        const width = pngData.width;
        const height = pngData.height;

        // BMP文件头
        const fileHeaderSize = 14;
        const infoHeaderSize = 40;
        const pixelOffset = fileHeaderSize + infoHeaderSize;

        // 创建像素数据(示例:黑色背景)
        const rowSize = Math.floor((width * 3 + 3) / 4) * 4;
        const pixelDataSize = rowSize * height;
        const fileSize = pixelOffset + pixelDataSize;

        const buffer = new ArrayBuffer(fileSize);
        const view = new DataView(buffer);

        // 写入BMP头
        this.writeBMPHeader(view, width, height, pixelDataSize);

        // 写入像素数据(黑色)
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const dstIndex = pixelOffset + ((height - 1 - y) * rowSize + x * 3);
                view.setUint8(dstIndex, 0);      // Blue
                view.setUint8(dstIndex + 1, 0);  // Green
                view.setUint8(dstIndex + 2, 0);  // Red
            }
        }

        return buffer;
    }

    static writeBMPHeader(view, width, height, pixelDataSize) {
        const fileHeaderSize = 14;
        const infoHeaderSize = 40;
        const pixelOffset = fileHeaderSize + infoHeaderSize;
        const fileSize = pixelOffset + pixelDataSize;

        // 文件头
        view.setUint8(0, 0x42);  // 'B'
        view.setUint8(1, 0x4D);  // 'M'
        view.setUint32(2, fileSize, true);
        view.setUint32(6, 0, true);
        view.setUint32(10, pixelOffset, true);

        // 信息头
        view.setUint32(14, infoHeaderSize, true);
        view.setInt32(18, width, true);
        view.setInt32(22, height, true);
        view.setUint16(26, 1, true);
        view.setUint16(28, 24, true);
        view.setUint32(30, 0, true);
        view.setUint32(34, pixelDataSize, true);
        view.setInt32(38, 2835, true);
        view.setInt32(42, 2835, true);
        view.setUint32(46, 0, true);
        view.setUint32(50, 0, true);
    }
}

建议

浏览器环境:使用方案一(Canvas API),最简单可靠 Node.js环境:使用方案二(Canvas库),功能完整 纯JavaScript:方案三仅作为演示,实际PNG解析复杂,建议使用库

安装依赖(Node.js)

# 安装Canvas库
npm install canvas

# 或者使用更轻量的PNG解析库
npm install pngjs

方案一和方案二都是生产可用的代码,可以直接复制使用。

相关推荐