<!DOCTYPE html>
<html>
<head>
<title>JPG to BMP Converter</title>
</head>
<body>
<input type="file" id="fileInput" accept="image/jpeg,image/jpg">
<button onclick="convertToBMP()">转换为BMP</button>
<canvas id="canvas" style="display:none;"></canvas>
<script>
// BMP文件头结构
class BMPEncoder {
constructor(width, height, data) {
this.width = width;
this.height = height;
this.data = data;
}
encode() {
const width = this.width;
const height = this.height;
const data = this.data;
// 每行字节数必须是4的倍数
const rowSize = Math.floor((width * 3 + 3) / 4) * 4;
const fileSize = 54 + rowSize * height;
// 创建ArrayBuffer
const buffer = new ArrayBuffer(fileSize);
const view = new DataView(buffer);
// BMP文件头 (14字节)
// 文件类型标识
view.setUint8(0, 0x42); // 'B'
view.setUint8(1, 0x4D); // 'M'
// 文件大小
view.setUint32(2, fileSize, true);
// 保留字段
view.setUint16(6, 0, true);
view.setUint16(8, 0, true);
// 像素数据偏移量
view.setUint32(10, 54, true);
// DIB头 (BITMAPINFOHEADER - 40字节)
view.setUint32(14, 40, true); // DIB头大小
view.setInt32(18, width, true); // 宽度
view.setInt32(22, height, true); // 高度
view.setUint16(26, 1, true); // 颜色平面数
view.setUint16(28, 24, true); // 每像素位数 (24位RGB)
view.setUint32(30, 0, true); // 压缩方式 (BI_RGB)
view.setUint32(34, 0, true); // 图像大小 (0表示不压缩)
view.setInt32(38, 0, true); // 水平分辨率
view.setInt32(42, 0, true); // 垂直分辨率
view.setUint32(46, 0, true); // 调色板颜色数
view.setUint32(50, 0, true); // 重要颜色数
// 像素数据 (BGR格式,非RGB)
let offset = 54;
// BMP存储是从下到上的,所以需要反向写入行
for (let y = height - 1; y >= 0; y--) {
for (let x = 0; x < width; x++) {
const pixelIndex = (y * width + x) * 4;
// 获取RGB值 (Canvas返回的是RGBA)
const r = data[pixelIndex];
const g = data[pixelIndex + 1];
const b = data[pixelIndex + 2];
// BMP使用BGR顺序
view.setUint8(offset++, b);
view.setUint8(offset++, g);
view.setUint8(offset++, r);
}
// 填充到4字节对齐
const padding = rowSize - width * 3;
for (let p = 0; p < padding; p++) {
view.setUint8(offset++, 0);
}
}
return buffer;
}
}
async function convertToBMP() {
const fileInput = document.getElementById('fileInput');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
if (!fileInput.files[0]) {
alert('请选择JPG文件');
return;
}
const file = fileInput.files[0];
// 验证文件类型
if (!file.type.match('image/jpeg') && !file.name.match(/\.jpe?g$/i)) {
alert('请选择JPG格式的图片');
return;
}
try {
// 创建Image对象
const img = new Image();
// 创建文件URL
const imgUrl = URL.createObjectURL(file);
// 等待图片加载
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = imgUrl;
});
// 设置Canvas尺寸
canvas.width = img.width;
canvas.height = img.height;
// 绘制图片到Canvas
ctx.drawImage(img, 0, 0);
// 获取图像数据
const imageData = ctx.getImageData(0, 0, img.width, img.height);
// 创建BMP编码器
const encoder = new BMPEncoder(img.width, img.height, imageData.data);
// 编码为BMP
const bmpBuffer = encoder.encode();
// 创建Blob并下载
const blob = new Blob([bmpBuffer], { type: 'image/bmp' });
const downloadUrl = URL.createObjectURL(blob);
// 创建下载链接
const a = document.createElement('a');
a.href = downloadUrl;
a.download = file.name.replace(/\.jpe?g$/i, '.bmp');
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// 清理URL
URL.revokeObjectURL(imgUrl);
URL.revokeObjectURL(downloadUrl);
console.log('转换完成!');
} catch (error) {
console.error('转换失败:', error);
alert('转换失败: ' + error.message);
}
}
</script>
</body>
</html>
如果需要更复杂的转换功能,可以使用第三方库:
<!-- 先引入html2canvas库 -->
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<script>
async function convertJpgToBmpUsingLibrary(jpgFile) {
return new Promise(async (resolve, reject) => {
try {
const img = new Image();
const reader = new FileReader();
reader.onload = async (e) => {
img.onload = async () => {
// 创建canvas
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
// 绘制图像
ctx.drawImage(img, 0, 0);
// 获取图像数据
const imageData = ctx.getImageData(0, 0, img.width, img.height);
// 使用相同的BMP编码器
const encoder = new BMPEncoder(img.width, img.height, imageData.data);
const bmpBuffer = encoder.encode();
resolve(bmpBuffer);
};
img.src = e.target.result;
};
reader.readAsDataURL(jpgFile);
} catch (error) {
reject(error);
}
});
}
</script>
如果需要服务器端转换,可以使用Node.js:
// 安装依赖:npm install sharp fs
const sharp = require('sharp');
const fs = require('fs');
async function convertJpgToBmp(inputPath, outputPath) {
try {
// 读取JPG文件
const image = sharp(inputPath);
// 获取图片信息
const metadata = await image.metadata();
// 转换为RAW RGB数据
const rawData = await image
.raw()
.toBuffer({ resolveWithObject: true });
// 创建BMP文件
await createBMPFile(
outputPath,
rawData.info.width,
rawData.info.height,
rawData.data
);
console.log(`转换完成: ${outputPath}`);
} catch (error) {
console.error('转换失败:', error);
}
}
function createBMPFile(outputPath, width, height, data) {
return new Promise((resolve, reject) => {
// 计算行大小(4字节对齐)
const rowSize = Math.floor((width * 3 + 3) / 4) * 4;
const fileSize = 54 + rowSize * height;
// 创建Buffer
const buffer = Buffer.alloc(fileSize);
// BMP文件头
buffer.write('BM', 0); // 文件类型
buffer.writeUInt32LE(fileSize, 2); // 文件大小
buffer.writeUInt32LE(0, 6); // 保留字段
buffer.writeUInt32LE(54, 10); // 像素数据偏移
// BITMAPINFOHEADER
buffer.writeUInt32LE(40, 14); // 头大小
buffer.writeInt32LE(width, 18); // 宽度
buffer.writeInt32LE(height, 22); // 高度
buffer.writeUInt16LE(1, 26); // 颜色平面数
buffer.writeUInt16LE(24, 28); // 每像素位数
buffer.writeUInt32LE(0, 30); // 压缩方式
buffer.writeUInt32LE(0, 34); // 图像大小
buffer.writeInt32LE(0, 38); // 水平分辨率
buffer.writeInt32LE(0, 42); // 垂直分辨率
buffer.writeUInt32LE(0, 46); // 调色板颜色数
buffer.writeUInt32LE(0, 50); // 重要颜色数
// 像素数据
let offset = 54;
// BMP是从下到上存储
for (let y = height - 1; y >= 0; y--) {
for (let x = 0; x < width; x++) {
const srcIndex = (y * width + x) * 3;
buffer[offset++] = data[srcIndex + 2]; // B
buffer[offset++] = data[srcIndex + 1]; // G
buffer[offset++] = data[srcIndex]; // R
}
// 填充
for (let p = width * 3; p < rowSize; p++) {
buffer[offset++] = 0;
}
}
// 写入文件
fs.writeFile(outputPath, buffer, (err) => {
if (err) reject(err);
else resolve();
});
});
}
// 使用示例
convertJpgToBmp('input.jpg', 'output.bmp');
选择适合你需求的方案即可!