1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| const rec2100ICC = new Uint8Array([ ])
function detectImageType(buf) { if (buf[0] === 0xFF && buf[1] === 0xD8) return 'jpeg'; if (buf[0] === 0x89 && buf[1] === 0x50) return 'png'; if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46) return 'webp'; return null; }
function injectICCToJPEG(buf, icc) { const header = [...'ICC_PROFILE\u0000'].map(c => c.charCodeAt(0)); const app2 = new Uint8Array(icc.length + header.length + 6); app2.set([0xFF, 0xE2], 0); const len = app2.length - 2; app2[2] = (len >> 8) & 0xFF; app2[3] = len & 0xFF; app2.set(header, 4); app2[4 + header.length] = 1; app2[5 + header.length] = 1; app2.set(icc, 6 + header.length); const out = new Uint8Array(buf.length + app2.length); out.set(buf.slice(0, 2), 0); out.set(app2, 2); out.set(buf.slice(2), 2 + app2.length); return out; }
function injectICCToPNG(buf, icc) { function crc32(buf) { let c = ~0; for (let i = 0; i < buf.length; i++) { c ^= buf[i]; for (let j = 0; j < 8; j++) { c = (c >>> 1) ^ (c & 1 ? 0xEDB88320 : 0); } } return ~c >>> 0; } const compressed = pako.deflate(icc); const profileName = 'ICCProfile'; const nameBytes = [...profileName].map(c => c.charCodeAt(0)); const nullByte = [0x00]; const compressionByte = [0x00]; const data = new Uint8Array(nameBytes.length + 1 + 1 + compressed.length); data.set(nameBytes, 0); data.set(nullByte, nameBytes.length); data.set(compressionByte, nameBytes.length + 1); data.set(compressed, nameBytes.length + 2);
const type = [0x69, 0x43, 0x43, 0x50]; const crc = crc32(new Uint8Array([...type, ...data]));
const chunk = new Uint8Array(4 + 4 + data.length + 4); const dv = new DataView(chunk.buffer); dv.setUint32(0, data.length); chunk.set(type, 4); chunk.set(data, 8); dv.setUint32(8 + data.length, crc);
const ihdrEnd = 8 + 25; const out = new Uint8Array(buf.length + chunk.length); out.set(buf.slice(0, ihdrEnd), 0); out.set(chunk, ihdrEnd); out.set(buf.slice(ihdrEnd), ihdrEnd + chunk.length); return out; }
function injectICCToWebP(buf, icc) { function readLE24(buf, offset) { return buf[offset] | (buf[offset + 1] << 8) | (buf[offset + 2] << 16); }
function makeVP8X(width, height) { const vp8x = new Uint8Array(18); const view = new DataView(vp8x.buffer); vp8x.set(new TextEncoder().encode("VP8X"), 0); view.setUint32(4, 10, true); vp8x[8] = 0x20; vp8x.set([0, 0, 0], 9); const w = width - 1, h = height - 1; vp8x[12] = w & 0xff; vp8x[13] = (w >> 8) & 0xff; vp8x[14] = (w >> 16) & 0xff; vp8x[15] = h & 0xff; vp8x[16] = (h >> 8) & 0xff; vp8x[17] = (h >> 16) & 0xff; return vp8x; }
function makeICCPChunk(data) { const paddedLength = data.length + (data.length % 2); const chunk = new Uint8Array(8 + paddedLength); chunk.set(new TextEncoder().encode("ICCP"), 0); new DataView(chunk.buffer).setUint32(4, data.length, true); chunk.set(data, 8); if (data.length % 2 === 1) chunk[8 + data.length] = 0; return chunk; }
const chunks = []; const view = new DataView(buf.buffer); let offset = 12; let foundVP8X = false; let width = 0, height = 0; let vp8xChunk = null;
while (offset + 8 <= buf.length) { const type = String.fromCharCode(...buf.slice(offset, offset + 4)); const size = view.getUint32(offset + 4, true); const chunkEnd = offset + 8 + size + (size % 2); const chunkData = buf.slice(offset, chunkEnd);
if (type === "VP8X") { foundVP8X = true; vp8xChunk = new Uint8Array(chunkData); vp8xChunk[8] |= 0x20; }
if (!foundVP8X && (type === "VP8 " || type === "VP8L")) { if (type === "VP8 ") { const w = view.getUint16(offset + 8 + 6, true); const h = view.getUint16(offset + 8 + 8, true); width = w & 0x3fff; height = h & 0x3fff; } else { const b = buf; const b0 = b[offset + 8 + 1]; const b1 = b[offset + 8 + 2]; const b2 = b[offset + 8 + 3]; const b3 = b[offset + 8 + 4]; width = ((b1 & 0x3F) << 8 | b0) + 1; height = ((b3 & 0x0F) << 10 | (b2 << 2) | (b1 >> 6)) + 1; } }
chunks.push(chunkData); offset = chunkEnd; }
if (!foundVP8X) { if (width === 0 || height === 0) { alert("无法推断尺寸"); return; } vp8xChunk = makeVP8X(width, height); chunks.unshift(vp8xChunk); }
const iccpChunk = makeICCPChunk(icc); chunks.splice(1, 0, iccpChunk);
const size = chunks.reduce((sum, c) => sum + c.length, 0); const out = new Uint8Array(12 + size); const dv = new DataView(out.buffer); out.set(new TextEncoder().encode("RIFF"), 0); dv.setUint32(4, out.length - 8, true); out.set(new TextEncoder().encode("WEBP"), 8);
let pos = 12; for (const chunk of chunks) { out.set(chunk, pos); pos += chunk.length; } return out; }
|