fix: replace BMP img.src decoder with pure-JS BMP parser for Brave/Firefox cross-browser support
This commit is contained in:
+77
-37
@@ -823,45 +823,85 @@ export class Viewer2D {
|
||||
|
||||
// If embedded image texture (BMP/DIB) is present, render it as a 3D Plane Mesh!
|
||||
if (imageDataUrl) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.naturalWidth || img.width || 100;
|
||||
canvas.height = img.naturalHeight || img.height || 100;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const pixels = imgData.data;
|
||||
// Ensure all alpha values are opaque (255) for GDI 32bpp BMPs
|
||||
for (let i = 3; i < pixels.length; i += 4) {
|
||||
pixels[i] = 255;
|
||||
}
|
||||
ctx.putImageData(imgData, 0, 0);
|
||||
try {
|
||||
// Pure-JS BMP decoder — works in ALL browsers (Brave/Firefox don't support data:image/bmp in <img>)
|
||||
const decodeBmpDataUrl = (dataUrl) => {
|
||||
const b64 = dataUrl.split(',')[1];
|
||||
const bin = atob(b64);
|
||||
const buf = new Uint8Array(bin.length);
|
||||
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
const planeGeom = new THREE.PlaneGeometry(width, Math.abs(height));
|
||||
const planeMat = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: false,
|
||||
});
|
||||
const mesh = new THREE.Mesh(planeGeom, planeMat);
|
||||
mesh.position.set(minX + width / 2, minY + Math.abs(height) / 2, z + 10);
|
||||
this._group.add(mesh);
|
||||
this._renderer.render(this._scene, this._camera);
|
||||
const dv = new DataView(buf.buffer);
|
||||
// BITMAPFILEHEADER (14 bytes)
|
||||
const sig = String.fromCharCode(buf[0], buf[1]);
|
||||
if (sig !== 'BM') throw new Error('Not a BMP file');
|
||||
const pixelOffset = dv.getUint32(10, true);
|
||||
// BITMAPINFOHEADER (40 bytes, starts at offset 14)
|
||||
const bmpWidth = dv.getInt32(18, true);
|
||||
const bmpHeight = dv.getInt32(22, true); // positive = bottom-up
|
||||
const bitCount = dv.getUint16(28, true);
|
||||
const absH = Math.abs(bmpHeight);
|
||||
const flipY = bmpHeight > 0; // bottom-up storage
|
||||
|
||||
const cvs = document.createElement('canvas');
|
||||
cvs.width = bmpWidth;
|
||||
cvs.height = absH;
|
||||
const ctx2 = cvs.getContext('2d');
|
||||
const imgData = ctx2.createImageData(bmpWidth, absH);
|
||||
const px = imgData.data;
|
||||
|
||||
if (bitCount === 32) {
|
||||
const bpr = bmpWidth * 4;
|
||||
for (let y = 0; y < absH; y++) {
|
||||
const srcRow = flipY ? (absH - 1 - y) : y;
|
||||
const srcOff = pixelOffset + srcRow * bpr;
|
||||
const dstOff = y * bmpWidth * 4;
|
||||
for (let x = 0; x < bmpWidth; x++) {
|
||||
const s = srcOff + x * 4;
|
||||
const d = dstOff + x * 4;
|
||||
px[d] = buf[s + 2]; // R (BMP stores BGR)
|
||||
px[d + 1] = buf[s + 1]; // G
|
||||
px[d + 2] = buf[s]; // B
|
||||
px[d + 3] = 255; // A — GDI 32bpp has zeroed alpha padding
|
||||
}
|
||||
}
|
||||
} else if (bitCount === 24) {
|
||||
const stride = Math.ceil(bmpWidth * 3 / 4) * 4; // row padded to 4 bytes
|
||||
for (let y = 0; y < absH; y++) {
|
||||
const srcRow = flipY ? (absH - 1 - y) : y;
|
||||
const srcOff = pixelOffset + srcRow * stride;
|
||||
const dstOff = y * bmpWidth * 4;
|
||||
for (let x = 0; x < bmpWidth; x++) {
|
||||
const s = srcOff + x * 3;
|
||||
const d = dstOff + x * 4;
|
||||
px[d] = buf[s + 2];
|
||||
px[d + 1] = buf[s + 1];
|
||||
px[d + 2] = buf[s];
|
||||
px[d + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed rendering CanvasTexture for OLE image:', err);
|
||||
}
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
console.error('Failed loading OLE imageDataUrl:', err);
|
||||
};
|
||||
img.src = imageDataUrl;
|
||||
ctx2.putImageData(imgData, 0, 0);
|
||||
return cvs;
|
||||
};
|
||||
|
||||
const bmpCanvas = decodeBmpDataUrl(imageDataUrl);
|
||||
const texture = new THREE.CanvasTexture(bmpCanvas);
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
const planeGeom = new THREE.PlaneGeometry(width, Math.abs(height));
|
||||
const planeMat = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: false,
|
||||
});
|
||||
const mesh = new THREE.Mesh(planeGeom, planeMat);
|
||||
mesh.position.set(minX + width / 2, minY + Math.abs(height) / 2, z + 10);
|
||||
this._group.add(mesh);
|
||||
this._renderer.render(this._scene, this._camera);
|
||||
} catch (err) {
|
||||
console.error('Failed rendering CanvasTexture for OLE image:', err);
|
||||
}
|
||||
} else {
|
||||
// Draw diagonal cross + overlay label fallback
|
||||
pushSeg(minX, minY, maxX, maxY, z, color);
|
||||
|
||||
Reference in New Issue
Block a user