方案一:使用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
方案一和方案二都是生产可用的代码,可以直接复制使用。