fix: replace BMP img.src decoder with pure-JS BMP parser for Brave/Firefox cross-browser support
This commit is contained in:
+62
-22
@@ -823,24 +823,70 @@ export class Viewer2D {
|
|||||||
|
|
||||||
// If embedded image texture (BMP/DIB) is present, render it as a 3D Plane Mesh!
|
// If embedded image texture (BMP/DIB) is present, render it as a 3D Plane Mesh!
|
||||||
if (imageDataUrl) {
|
if (imageDataUrl) {
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
try {
|
try {
|
||||||
const canvas = document.createElement('canvas');
|
// Pure-JS BMP decoder — works in ALL browsers (Brave/Firefox don't support data:image/bmp in <img>)
|
||||||
canvas.width = img.naturalWidth || img.width || 100;
|
const decodeBmpDataUrl = (dataUrl) => {
|
||||||
canvas.height = img.naturalHeight || img.height || 100;
|
const b64 = dataUrl.split(',')[1];
|
||||||
const ctx = canvas.getContext('2d');
|
const bin = atob(b64);
|
||||||
if (ctx) {
|
const buf = new Uint8Array(bin.length);
|
||||||
ctx.drawImage(img, 0, 0);
|
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
||||||
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);
|
|
||||||
|
|
||||||
const texture = new THREE.CanvasTexture(canvas);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx2.putImageData(imgData, 0, 0);
|
||||||
|
return cvs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bmpCanvas = decodeBmpDataUrl(imageDataUrl);
|
||||||
|
const texture = new THREE.CanvasTexture(bmpCanvas);
|
||||||
texture.colorSpace = THREE.SRGBColorSpace;
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
texture.needsUpdate = true;
|
texture.needsUpdate = true;
|
||||||
const planeGeom = new THREE.PlaneGeometry(width, Math.abs(height));
|
const planeGeom = new THREE.PlaneGeometry(width, Math.abs(height));
|
||||||
@@ -853,15 +899,9 @@ export class Viewer2D {
|
|||||||
mesh.position.set(minX + width / 2, minY + Math.abs(height) / 2, z + 10);
|
mesh.position.set(minX + width / 2, minY + Math.abs(height) / 2, z + 10);
|
||||||
this._group.add(mesh);
|
this._group.add(mesh);
|
||||||
this._renderer.render(this._scene, this._camera);
|
this._renderer.render(this._scene, this._camera);
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed rendering CanvasTexture for OLE image:', err);
|
console.error('Failed rendering CanvasTexture for OLE image:', err);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
img.onerror = (err) => {
|
|
||||||
console.error('Failed loading OLE imageDataUrl:', err);
|
|
||||||
};
|
|
||||||
img.src = imageDataUrl;
|
|
||||||
} else {
|
} else {
|
||||||
// Draw diagonal cross + overlay label fallback
|
// Draw diagonal cross + overlay label fallback
|
||||||
pushSeg(minX, minY, maxX, maxY, z, color);
|
pushSeg(minX, minY, maxX, maxY, z, color);
|
||||||
|
|||||||
Reference in New Issue
Block a user