/* This software is licensed under the MIT License. Copyright (c) 2016 desudesutalk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. https://github.com/desudesutalk/f5stegojs This library is based on https://github.com/owencm/js-steg by Owen Campbell- Moore. Decoder and encoder was optimized for speed, F5 algorithm and metadata manipulation utils was added to library. Original code was released under MIT and Apache licenses, so here follows original licenses of Owen code: jpeg decoder license: Modified JPEG decoder for Steganography by Owen Campbell-Moore, based on one released by Adobe. Copyright 2011 notmasteryet Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. jpeg encoder license: JPEG encoder ported to JavaScript, optimized by Andreas Ritter (www.bytestrom.eu, 11/2009) and made suitable for steganography by Owen Campbell-Moore (www.owencampbellmoore.com, 03/13) Based on v 0.9a Licensed under the MIT License Copyright (c) 2009 Andreas Ritter Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Copyright (c) 2008, Adobe Systems Incorporated All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Adobe Systems Incorporated nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import { BitstreamWriter, revbyte } from "./bitstream"; /* global define, module, exports */ /* jshint sub:true */ // Standard Huffman tables for coder initialization // =========================================================================================================== function* f5get() { let extrBit = 0; //var pm = this.#stegShuffle(coeff), // gamma = pm.gamma, // gammaI = 0; let k = 0; for (let i = 0; i < 4; ++i) { const b = (yield); k |= b << i; } k = (k & 15) + 1; let toread = 8; // length is on 16 bits, unless bit 0 on the second byte is 1, in which case it's 23 bits let len = 0; while (toread--) { const b = (yield); len = len * 2 + b; } const b = yield; toread += 8; if (b) toread += 7; else len *= 2; while (toread--) { const b = (yield); len = len * 2 + b; } const rlen = revbyte(len, b ? 23 : 16); len = rlen; if (len > 256) throw new Error("Too big for Smash"); len *= 8; // bytes to bits // k must be 1 const chunks: Uint8Array[] = []; const bw = new BitstreamWriter({ write(chunk) { chunks.push(chunk); }, }); while (len) { extrBit = yield; bw.write(1, extrBit); len--; } bw.end(); return Buffer.concat(chunks).slice(0, rlen); } const bitcode = new Array(65535), category = new Array(65535), std_dc_luminance_nrcodes = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], std_dc_luminance_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], std_ac_luminance_nrcodes = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d], std_ac_luminance_values = [ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa ], std_dc_chrominance_nrcodes = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], std_dc_chrominance_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], std_ac_chrominance_nrcodes = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77], std_ac_chrominance_values = [ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa ]; function _initCategoryNumber() { let nrlower = 1; let nrupper = 2; for (let cat = 1; cat <= 15; cat++) { //Positive numbers for (let nr = nrlower; nr < nrupper; nr++) { category[32767 + nr] = cat; bitcode[32767 + nr] = []; bitcode[32767 + nr][1] = cat; bitcode[32767 + nr][0] = nr; } //Negative numbers for (let nrneg = -(nrupper - 1); nrneg <= -nrlower; nrneg++) { category[32767 + nrneg] = cat; bitcode[32767 + nrneg] = []; bitcode[32767 + nrneg][1] = cat; bitcode[32767 + nrneg][0] = nrupper - 1 + nrneg; } nrlower <<= 1; nrupper <<= 1; } } _initCategoryNumber(); function _computeHuffmanTbl(nrcodes: number[], std_table: number[]) { let codevalue = 0; let pos_in_table = 0; const HT: number[][] = []; for (let k = 1; k <= 16; k++) { for (let j = 1; j <= nrcodes[k]; j++) { HT[std_table[pos_in_table]] = []; HT[std_table[pos_in_table]][0] = codevalue; HT[std_table[pos_in_table]][1] = k; pos_in_table++; codevalue++; } codevalue *= 2; } return HT; } const YDC_HT = _computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values), UVDC_HT = _computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values), YAC_HT = _computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values), UVAC_HT = _computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values); type FrameComponentType = { componentId: number, h: number, v: number, quantizationTable: number; huffmanTableDC?: Uint16Array; huffmanTableAC?: Uint16Array; } | { componentId: number, blocks: Int16Array, blocksDC: Int16Array; blocksPerLine: number; blocksPerColumn: number; blocksPerLineForMcu: number; blocksPerColumnForMcu: number; h: number, v: number, quantizationTable: number huffmanTableDC: Uint16Array; huffmanTableAC: Uint16Array; pred: number; }; type DiscriminateUnion = T extends Record ? T : never; type FrameType = { extended: boolean, progressive: boolean, precision: number, scanLines: number, samplesPerLine: number, components: (FrameComponentType)[]; mcusPerLine?: number, mcusPerColumn?: number, componentIds: Record, maxH: number, maxV: number }; export class f5stego { constructor(key: ArrayLike, private maxPixels: number = 4096 * 4096) { } embed(image: Uint8Array, data: ArrayLike, k: number) { this.parse(image); this.f5put(data, k); return this.pack(); } extract(image: Uint8Array) { try { this.gengen = f5get(); this.gengen.next(); // run shit this.parse(image, true); return this.f5get(); } catch (e) { if (e instanceof Buffer) return e; throw e; } } #_raw?: Uint8Array; #jfif?: any; #APPn?: any; #qts?: any[]; #frame: FrameType | null = null; #tail: Uint8Array | null = null; #_f5write(coeff: Int16Array, data: ArrayLike, k: number) { const coeff_count = coeff.length; let _changed = 0, _embedded = 0, _examined = 0, _thrown = 0, shuffled_index = 0, i, n, ii: number; //let { gamma, pm } = this.#stegShuffle(coeff_count); //let gammaI = 0; let next_bit_to_embed = 0, byte_to_embed = data.length, data_idx = 0, available_bits_to_embed = 0; n = (1 << k) - 1; byte_to_embed = k - 1; byte_to_embed ^= 0; // nop //byte_to_embed ^= gamma[gammaI++]; next_bit_to_embed = byte_to_embed & 1; byte_to_embed >>= 1; available_bits_to_embed = 3; for (ii = 0; ii < coeff_count; ii++) { //shuffled_index = pm[ii]; shuffled_index = ii; if (shuffled_index % 64 === 0 || coeff[shuffled_index] === 0) continue; const cc = coeff[shuffled_index]; _examined++; if (cc > 0 && (cc & 1) != next_bit_to_embed) { coeff[shuffled_index]--; _changed++; } else if (cc < 0 && (cc & 1) == next_bit_to_embed) { coeff[shuffled_index]++; _changed++; } if (coeff[shuffled_index] !== 0) { _embedded++; if (available_bits_to_embed === 0) { if (k != 1 || data_idx >= data.length) break; byte_to_embed = data[data_idx++]; //byte_to_embed ^= gamma[gammaI++]; byte_to_embed ^= 0; // nop available_bits_to_embed = 8; } next_bit_to_embed = byte_to_embed & 1; byte_to_embed >>= 1; available_bits_to_embed--; } else { _thrown++; } } if (k == 1 && _embedded < data.length * 8) throw 'capacity exceeded ' + (_embedded / 8) + ' ' + data.length; if (k != 1) { //ii--; let is_last_byte = false, k_bits_to_embed = 0; while (!is_last_byte || (available_bits_to_embed !== 0 && is_last_byte)) { k_bits_to_embed = 0; for (i = 0; i < k; i++) { if (available_bits_to_embed === 0) { if (data_idx >= data.length) { is_last_byte = true; break; } byte_to_embed = data[data_idx++]; //byte_to_embed ^= gamma[gammaI++]; byte_to_embed ^= 0; // nop available_bits_to_embed = 8; } next_bit_to_embed = byte_to_embed & 1; byte_to_embed >>= 1; available_bits_to_embed--; k_bits_to_embed |= next_bit_to_embed << i; } const code_word: number[] = []; let ci: number | null = null; for (i = 0; i < n; i++) { while (true) { if (++ii >= coeff_count) { throw 'capacity exceeded ' + (_embedded / 8); } //ci = pm[ii]; ci = ii; if (ci % 64 !== 0 && coeff[ci] !== 0) break; } code_word.push(ci); } _examined += n; while (true) { var vhash = 0, extracted_bit; for (i = 0; i < code_word.length; i++) { if (coeff[code_word[i]] > 0) { extracted_bit = coeff[code_word[i]] & 1; } else { extracted_bit = 1 - (coeff[code_word[i]] & 1); } if (extracted_bit == 1) vhash ^= i + 1; } i = vhash ^ k_bits_to_embed; if (!i) { _embedded += k; break; } i--; coeff[code_word[i]] += coeff[code_word[i]] < 0 ? 1 : -1; _changed++; if (coeff[code_word[i]] === 0) { _thrown++; code_word.splice(i, 1); while (true) { if (++ii >= coeff_count) { throw 'capacity exceeded ' + (_embedded / 8); } //ci = pm[ii]; ci = ii; if (ci % 64 !== 0 && coeff[ci] !== 0) break; } _examined++; code_word.push(ci); } else { _embedded += k; break; } } } } return { 'k': k, 'embedded': _embedded / 8, 'examined': _examined, 'changed': _changed, 'thrown': _thrown, 'efficiency': (_embedded / _changed).toFixed(2) }; } f5put(data: ArrayLike, k: number) { if (!this.#frame) throw "Parser not run"; let t, i, comp = this.#frame.components[0]; // Looks funny, but who knows? // From the other hand you need ~80MB jpeg to hide 8MB of data and this will be bigger than 4096x4096 pixels if (data.length > 8388607) throw 'Data too big. Max 8388607 bytes allowed.'; if (data.length < 32768) { t = new Uint8Array(2 + data.length); t[0] = data.length & 255; t[1] = data.length >>> 8; t.set(data, 2); } else { t = new Uint8Array(3 + data.length); t[0] = data.length & 255; t[1] = ((data.length >>> 8) & 127) + 128; t[2] = data.length >>> 15; t.set(data, 3); } if (comp.componentId != 1) { for (i = 0; i < this.#frame.components.length; i++) { if (this.#frame.components[i].componentId == 1) { comp = this.#frame.components[i]; break; } } } if (!('blocks' in comp)) { throw "Blocks failed to be parsed"; } return this.#_f5write(comp.blocks, t, k); } f5get() { if (!this.#frame) throw "Parser not run"; let comp = this.#frame.components[0]; if (comp.componentId != 1) { for (let i = 0; i < this.#frame.components.length; i++) { if (this.#frame.components[i].componentId == 1) { comp = this.#frame.components[i]; break; } } } if (!('blocks' in comp)) { throw "Blocks failed to be parsed"; } const coeff = new Int16Array(comp.blocks.length); coeff.set(comp.blocks); let pos = -1, extrBit = 0, cCount = coeff.length - 1; //var pm = this.#stegShuffle(coeff), // gamma = pm.gamma, // gammaI = 0; let n, k = 0; let out = new Uint8Array((coeff.length / 8) | 0), extrByte = 0, outPos = 0, bitsAvail = 0, code = 0, hash = 0; while (bitsAvail < 4) { pos++; //console.log(pos) if (coeff[pos] === 0) { continue; } extrBit = coeff[pos] & 1; if (coeff[pos] < 0) { extrBit = 1 - extrBit; } k |= extrBit << bitsAvail; bitsAvail++; } //k = (k ^ gamma[gammaI++] & 15) + 1; k = (k & 15) + 1; n = (1 << k) - 1; bitsAvail = 0; if (k == 1) { while (pos < cCount) { pos++; if (coeff[pos] === 0) { continue; } extrBit = coeff[pos] & 1; if (coeff[pos] < 0) { extrBit = 1 - extrBit; } extrByte |= extrBit << bitsAvail; bitsAvail++; if (bitsAvail == 8) { out[outPos++] = extrByte; //out[outPos++] = extrByte ^ gamma[gammaI++]; extrByte = 0; bitsAvail = 0; } } } else { while (pos < cCount) { pos++; if (coeff[pos] === 0) { continue; } extrBit = coeff[pos] & 1; if (coeff[pos] < 0) { extrBit = 1 - extrBit; } hash ^= extrBit * ++code; if (code == n) { extrByte |= hash << bitsAvail; bitsAvail += k; code = 0; hash = 0; while (bitsAvail >= 8) { //out[outPos++] = (extrByte & 0xFF) ^ gamma[gammaI++]; out[outPos++] = (extrByte & 0xFF); bitsAvail -= 8; extrByte = extrByte >> 8; } } } } while (bitsAvail > 0) { //out[outPos++] = (extrByte & 0xFF) ^ gamma[gammaI++]; out[outPos++] = (extrByte & 0xFF); bitsAvail -= 8; extrByte = extrByte >> 8; } let s = 2, l = out[0]; if (out[1] & 128) { s++; l += ((out[1] & 127) << 8) + (out[2] << 15); } else { l += out[1] << 8; } return out.subarray(s, s + l); } gengen: Generator; parse(data: Uint8Array, tolerant = false) { let offset = 0; function _buildHuffmanTable(nrcodes: Uint8Array, values: Uint8Array) { let codevalue = 0, pos_in_table = 0, HT = new Uint16Array(65536); for (let k = 0; k < 16; k++) { for (let j = 0; j < nrcodes[k]; j++) { for (let i = codevalue << (15 - k), cntTo = ((codevalue + 1) << (15 - k)); i < cntTo; i++) { HT[i] = values[pos_in_table] + ((k + 1) << 8); } pos_in_table++; codevalue++; } codevalue *= 2; } return HT; } const decodeScan = (data: ArrayLike, offset: number, frame: FrameType, components: DiscriminateUnion[], resetInterval: number | undefined, spectralStart: number, spectralEnd: number, successivePrev: number, successive: number) => { let startOffset = offset, bitsData = 0, bitsCount = 0, eobrun = 0, p1 = 1 << successive, /* 1 in the bit position being coded */ m1 = -1 << successive; /* -1 in the bit position being coded */ const prevpos = 0; const decodeBaseline = (component: typeof components[0], pos: number) => { while (bitsCount < 16) { bitsData = (bitsData << 8) + (data[offset] | 0); bitsCount += 8; if (data[offset] == 0xFF) offset++; offset++; } let t = component.huffmanTableDC[(bitsData >>> (bitsCount - 16)) & 0xFFFF]; if (!t) throw "invalid huffman sequence"; bitsCount -= t >>> 8; t &= 255; let diff = 0; if (t !== 0) { while (bitsCount < t) { bitsData = (bitsData << 8) + data[offset++]; if ((bitsData & 0xff) == 0xFF) offset++; bitsCount += 8; } diff = (bitsData >>> (bitsCount - t)) & ((1 << t) - 1); bitsCount -= t; if (diff < 1 << (t - 1)) diff += (-1 << t) + 1; } component.blocksDC[pos >> 6] = (component.pred += diff); let k = 1, s, r; while (k < 64) { while (bitsCount < 16) { bitsData = (bitsData << 8) + (data[offset] | 0); bitsCount += 8; if (data[offset] == 0xFF) offset++; offset++; } s = component.huffmanTableAC![(bitsData >>> (bitsCount - 16)) & 0xFFFF]; if (!s) throw "invalid huffman sequence"; bitsCount -= s >>> 8; r = (s >> 4) & 15; s &= 15; if (s === 0) { if (r < 15) { break; } k += 16; continue; } k += r; while (bitsCount < s) { bitsData = (bitsData << 8) + data[offset++]; if ((bitsData & 0xff) == 0xFF) offset++; bitsCount += 8; } component.blocks[pos + k] = (bitsData >>> (bitsCount - s)) & ((1 << s) - 1); if (component.blocks[pos + k] < 1 << (s - 1)) component.blocks[pos + k] += (-1 << s) + 1; bitsCount -= s; k++; } }; function decodeDCFirst(component: typeof components[0], pos: number) { let diff = 0; while (bitsCount < 16) { bitsData = (bitsData << 8) + (data[offset] | 0); bitsCount += 8; if (data[offset] == 0xFF) offset++; offset++; } let t = component.huffmanTableDC[(bitsData >>> (bitsCount - 16)) & 0xFFFF]; if (!t) throw "invalid huffman sequence"; bitsCount -= t >>> 8; t &= 255; if (t !== 0) { while (bitsCount < t) { bitsData = (bitsData << 8) + data[offset++]; if ((bitsData & 0xff) == 0xFF) offset++; bitsCount += 8; } diff = (bitsData >>> (bitsCount - t)) & ((1 << t) - 1); bitsCount -= t; if (diff < 1 << (t - 1)) diff += (-1 << t) + 1; } component.blocksDC[pos >> 6] = (component.pred += diff << successive); } function decodeDCSuccessive(component: typeof components[0], pos: number) { if (!bitsCount) { bitsData = data[offset++]; if (bitsData == 0xFF) offset++; bitsCount = 8; } component.blocksDC[pos >> 6] |= ((bitsData >>> --bitsCount) & 1) << successive; } if (!frame) throw "Frame not parsed yet"; function decodeACFirst(component: typeof components[0], pos: number) { if (eobrun > 0) { eobrun--; return; } let k = spectralStart, s, r; while (k <= spectralEnd) { while (bitsCount < 16) { bitsData = (bitsData << 8) + (data[offset] | 0); bitsCount += 8; if (data[offset] == 0xFF) offset++; offset++; } s = component.huffmanTableAC[(bitsData >>> (bitsCount - 16)) & 0xFFFF]; if (!s) throw "invalid huffman sequence"; bitsCount -= s >>> 8; r = (s >> 4) & 15; s &= 15; if (s === 0) { if (r != 15) { eobrun = (1 << r) - 1; if (r) { while (bitsCount < r) { bitsData = (bitsData << 8) + data[offset++]; if ((bitsData & 0xff) == 0xFF) offset++; bitsCount += 8; } eobrun += (bitsData >>> (bitsCount - r)) & ((1 << r) - 1); bitsCount -= r; } break; } k += 16; continue; } k += r; while (bitsCount < s) { bitsData = (bitsData << 8) + data[offset++]; if ((bitsData & 0xff) == 0xFF) offset++; bitsCount += 8; } component.blocks[pos + k] = (bitsData >>> (bitsCount - s)) & ((1 << s) - 1); bitsCount -= s; if (component.blocks[pos + k] < 1 << (s - 1)) component.blocks[pos + k] += (-1 << s) + 1; component.blocks[pos + k] *= p1; k++; } } function decodeACSuccessive(component: typeof components[0], pos: number) { let k = spectralStart, r, s; if (frame == null) throw "Frame not defined"; if (!eobrun) { while (k <= spectralEnd) { while (bitsCount < 16) { bitsData = (bitsData << 8) + (data[offset] | 0); bitsCount += 8; if (data[offset] == 0xFF) offset++; offset++; } s = component.huffmanTableAC[(bitsData >>> (bitsCount - 16)) & 0xFFFF]; if (!s) throw "invalid huffman sequence"; bitsCount -= s >>> 8; r = (s >> 4) & 15; s &= 15; if (s) { if (s != 1) throw "bad jpeg"; if (!bitsCount) { bitsData = data[offset++]; if (bitsData == 0xFF) offset++; bitsCount = 8; } s = ((bitsData >>> --bitsCount) & 1) ? p1 : m1; } else { if (r != 15) { eobrun = (1 << r); if (r) { while (bitsCount < r) { bitsData = (bitsData << 8) + data[offset++]; if ((bitsData & 0xff) == 0xFF) offset++; bitsCount += 8; } eobrun += (bitsData >>> (bitsCount - r)) & ((1 << r) - 1); bitsCount -= r; } break; } } while (k <= spectralEnd) { if (component.blocks[pos + k]) { if (!bitsCount) { bitsData = data[offset++]; if (bitsData == 0xFF) offset++; bitsCount = 8; } component.blocks[pos + k] += ((bitsData >>> --bitsCount) & 1) * (component.blocks[pos + k] >= 0 ? p1 : m1); } else { if (--r < 0) break; } k++; } if (s) component.blocks[pos + k] = s; k++; } } if (eobrun) { while (k <= spectralEnd) { if (component.blocks[pos + k]) { if (!bitsCount) { bitsData = data[offset++]; if (bitsData == 0xFF) offset++; bitsCount = 8; } component.blocks[pos + k] += ((bitsData >>> --bitsCount) & 1) * (component.blocks[pos + k] >= 0 ? p1 : m1); } k++; } eobrun--; } } let decodeFn; if (frame.progressive) { if (spectralStart === 0) decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; else decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; } else { decodeFn = decodeBaseline; } let marker, mcuExpected, i, j, k, n, mcusPerLine, mcusPerRow, x, y; let lastflushidx = 0; const flushBits = () => { if (!this.gengen) return; const component = components.find(e => e.componentId == 1)!; // EMIT BITS HERE while (component.blocks[lastflushidx + 1] !== undefined) { const blk = component.blocks[lastflushidx]; if (blk != 0) { const v = (blk < 0) ? 1 - (blk & 1) : (blk & 1); const it = this.gengen.next(v); if (it.done) { throw it.value; } } lastflushidx++; } }; if (components.length == 1) { mcusPerLine = components[0].blocksPerLine; mcusPerRow = components[0].blocksPerColumn; mcuExpected = mcusPerRow * mcusPerLine; if (!resetInterval) resetInterval = mcuExpected; n = resetInterval; components[0].pred = 0; eobrun = 0; for (y = 0; y < mcusPerRow; y++) { for (x = 0; x < mcusPerLine; x++) { if (!n) { n = resetInterval; components[0].pred = 0; eobrun = 0; // find marker offset -= (bitsCount / 8) | 0; if (data[offset - 1] == 0xFF) offset--; bitsCount = 0; marker = (data[offset] << 8) | data[offset + 1]; if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx offset += 2; } else { if (marker <= 0xFF00) { throw "bad jpeg"; } break; } } n--; for (i = 0; i < components.length; i++) { decodeFn(components[i], (y * components[i].blocksPerLineForMcu + x) * 64); } } flushBits(); } } else { mcusPerLine = frame.mcusPerLine; mcusPerRow = frame.mcusPerColumn; mcuExpected = mcusPerRow! * mcusPerLine!; if (!resetInterval) resetInterval = mcuExpected; n = resetInterval; for (i = 0; i < components.length; i++) components[i].pred = 0; eobrun = 0; for (y = 0; y < mcusPerRow!; y++) { for (x = 0; x < mcusPerLine!; x++) { if (!n) { n = resetInterval; for (i = 0; i < components.length; i++) components[i].pred = 0; eobrun = 0; // find marker offset -= (bitsCount / 8) | 0; if (data[offset - 1] == 0xFF) offset--; bitsCount = 0; marker = (data[offset] << 8) | data[offset + 1]; if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx offset += 2; } else { if (marker <= 0xFF00) { throw "bad jpeg"; } break; } } n--; for (i = 0; i < components.length; i++) { for (j = 0; j < components[i].v; j++) { for (k = 0; k < components[i].h; k++) { decodeFn(components[i], ((y * components[i].v + j) * components[i].blocksPerLineForMcu + x * components[i].h + k) * 64); } } } } flushBits(); } } offset -= (bitsCount / 8) | 0; if (data[offset - 1] == 0xFF) offset--; return offset - startOffset; }; function readUint16() { const value = (data[offset] << 8) | data[offset + 1]; offset += 2; return value; } function readDataBlock() { const length = readUint16(); const array = data.subarray(offset, offset + length - 2); offset += array.length; return array; } this.#_raw = data; this.#jfif = null; this.#APPn = []; this.#qts = []; this.#frame = null; this.#tail = null; let markerHi, markerLo, i, j, resetInterval, component; const huffmanTablesAC: Uint16Array[] = []; const huffmanTablesDC: Uint16Array[] = []; while (1) { if (offset >= data.length) { if (tolerant) break; throw "unexpected EOF"; } markerHi = data[offset++]; markerLo = data[offset++]; if (markerHi == 0xFF) { if (markerLo == 0xE0) { //APP0 - JFIF header this.#jfif = readDataBlock(); } if ((markerLo > 0xE0 && markerLo < 0xF0) || markerLo == 0xFE) { //APPn + COM this.#APPn.push({ 'app': markerLo, 'data': readDataBlock() }); } if (markerLo == 0xDB) { // DQT (Define Quantization Tables) this.#qts.push(readDataBlock()); } if (markerLo >= 0xC0 && markerLo <= 0xC2) { // SOF0 (Start of Frame, Baseline DCT) // SOF1 (Start of Frame, Extended DCT) // SOF2 (Start of Frame, Progressive DCT) if (this.#frame) throw "Only single frame JPEGs supported"; readUint16(); // skip data length this.#frame = { 'extended': (markerLo === 0xC1), 'progressive': (markerLo === 0xC2), 'precision': data[offset++], 'scanLines': readUint16(), 'samplesPerLine': readUint16(), 'components': [], 'componentIds': {}, 'maxH': 1, 'maxV': 1 }; if (this.#frame.scanLines * this.#frame.samplesPerLine > this.maxPixels) throw "Image is too big."; var componentsCount = data[offset++], componentId; let maxH = 0, maxV = 0; for (i = 0; i < componentsCount; i++) { componentId = data[offset]; const h = data[offset + 1] >> 4; const v = data[offset + 1] & 15; if (maxH < h) maxH = h; if (maxV < v) maxV = v; const qId = data[offset + 2]; const l = this.#frame.components.push({ componentId: componentId, h, v, quantizationTable: qId }); this.#frame.componentIds[componentId] = l - 1; offset += 3; } this.#frame.maxH = maxH; this.#frame.maxV = maxV; const mcusPerLine = Math.ceil(this.#frame.samplesPerLine / 8 / maxH); const mcusPerColumn = Math.ceil(this.#frame.scanLines / 8 / maxV); for (i = 0; i < this.#frame.components.length; i++) { component = this.#frame.components[i]; const blocksPerLine = Math.ceil(Math.ceil(this.#frame.samplesPerLine / 8) * component.h / maxH); const blocksPerColumn = Math.ceil(Math.ceil(this.#frame.scanLines / 8) * component.v / maxV); const blocksPerLineForMcu = mcusPerLine * component.h; const blocksPerColumnForMcu = mcusPerColumn * component.v; this.#frame.components[i] = { ...component, blocks: new Int16Array(blocksPerColumnForMcu * blocksPerLineForMcu * 64), blocksDC: new Int16Array(blocksPerColumnForMcu * blocksPerLineForMcu), blocksPerLine: blocksPerLine, blocksPerColumn: blocksPerColumn, blocksPerLineForMcu: blocksPerLineForMcu, blocksPerColumnForMcu: blocksPerColumnForMcu, }; } this.#frame.mcusPerLine = mcusPerLine; this.#frame.mcusPerColumn = mcusPerColumn; } if (markerLo == 0xC4) { // DHT (Define Huffman Tables) const huffmanLength = readUint16(); for (i = 2; i < huffmanLength;) { const huffmanTableSpec = data[offset++]; const codeLengths = new Uint8Array(16); let codeLengthSum = 0; for (j = 0; j < 16; j++, offset++) codeLengthSum += (codeLengths[j] = data[offset]); const huffmanValues = new Uint8Array(codeLengthSum); for (j = 0; j < codeLengthSum; j++, offset++) huffmanValues[j] = data[offset]; i += 17 + codeLengthSum; const v = _buildHuffmanTable(codeLengths, huffmanValues); if ((huffmanTableSpec >> 4) === 0) huffmanTablesDC[huffmanTableSpec & 15] = v; else huffmanTablesAC[huffmanTableSpec & 15] = v; } } if (markerLo == 0xDD) { // DRI (Define Restart Interval) resetInterval = readUint16(); } if (markerLo == 0xDA) { // SOS (Start of Scan) if (this.#frame == null) throw "SOS before SOF"; readUint16(); const selectorsCount = data[offset++]; const components: FrameComponentType[] = []; for (i = 0; i < selectorsCount; i++) { const componentIndex = this.#frame.componentIds[data[offset++]]; component = this.#frame.components[componentIndex]; const tableSpec = data[offset++]; component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; components.push(component); } const spectralStart = data[offset++]; const spectralEnd = data[offset++]; const successiveApproximation = data[offset++]; const processed = decodeScan(data, offset, this.#frame, components as any, resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15); offset += processed; } if (markerLo == 0xD9) { // EOI (End of image) break; } } else { if (data[offset - 3] == 0xFF && data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { // could be incorrect encoding -- last 0xFF byte of the previous // block was eaten by the encoder offset -= 3; } while (data[offset] != 0xFF && offset < data.length) { // file could be damaged and have some extra data between blocks offset++; } if (data[offset] != 0xFF) { throw "bad jpeg "; } } } if (!this.#frame) throw 'bad jpeg'; if (offset < data.length) this.#tail = data.subarray(offset); return this; } pack() { let byteout: Uint8Array; let bytenew: number; let bytepos: number; let poslast: number; let outpos: number; let byte: number; // IO functions function writeByte(value: number) { let t; byteout[outpos++] = value; if (outpos > poslast) { t = new Uint8Array(byteout.length * 2); t.set(byteout); byteout = t; poslast = t.length - 128; } } function writeWord(value: number) { writeByte((value >> 8) & 0xFF); writeByte((value) & 0xFF); } function writeBlock(block: Uint8Array) { let t; if (outpos + block.length > poslast) { t = new Uint8Array(byteout.length * 2 + block.length); t.set(byteout); byteout = t; poslast = t.length - 128; } byteout.set(block, outpos); outpos += block.length; } function writeAPP0(self: f5stego) { writeWord(0xFFE0); // marker if (!self.#jfif) { writeWord(16); // length writeByte(0x4A); // J writeByte(0x46); // F writeByte(0x49); // I writeByte(0x46); // F writeByte(0); // = "JFIF",'\0' writeByte(1); // versionhi writeByte(1); // versionlo writeByte(0); // xyunits writeWord(1); // xdensity writeWord(1); // ydensity writeByte(0); // thumbnwidth writeByte(0); // thumbnheight } else { writeWord(self.#jfif.length + 2); // length writeBlock(self.#jfif); } } function writeDQT(self: f5stego) { for (let i = 0; i < self.#qts!.length; i++) { writeWord(0xFFDB); // marker writeWord(self.#qts![i].length + 2); // length writeBlock(self.#qts![i]); } } function writeAPPn(self: f5stego) { for (let i = 0; i < self.#APPn.length; i++) { writeWord(0xFF00 | self.#APPn[i].app); writeWord(self.#APPn[i].data.length + 2); writeBlock(self.#APPn[i].data); } } function writeSOF0(self: f5stego) { if (!self.#frame) throw "Frame not ready"; writeWord(0xFFC0); // marker writeWord(8 + self.#frame.components.length * 3); // length writeByte(self.#frame.precision); // precision writeWord(self.#frame.scanLines); writeWord(self.#frame.samplesPerLine); writeByte(self.#frame.components.length); // nrofcomponents for (let i = 0; i < self.#frame.components.length; i++) { const c = self.#frame.components[i]; writeByte(c.componentId); writeByte(c.h << 4 | c.v); writeByte(c.quantizationTable); } } function writeDHT(self: f5stego) { if (!self.#frame) throw "Frame not ready"; writeWord(0xFFC4); // marker writeWord(31); // length writeByte(0); // HTYDCinfo for (let i = 0; i < 16; i++) { writeByte(std_dc_luminance_nrcodes[i + 1]); } for (let j = 0; j <= 11; j++) { writeByte(std_dc_luminance_values[j]); } writeWord(0xFFC4); // marker writeWord(181); // length writeByte(0x10); // HTYACinfo for (let k = 0; k < 16; k++) { writeByte(std_ac_luminance_nrcodes[k + 1]); } for (let l = 0; l <= 161; l++) { writeByte(std_ac_luminance_values[l]); } if (self.#frame.components.length != 1) { writeWord(0xFFC4); // marker writeWord(31); // length writeByte(1); // HTUDCinfo for (let m = 0; m < 16; m++) { writeByte(std_dc_chrominance_nrcodes[m + 1]); } for (let n = 0; n <= 11; n++) { writeByte(std_dc_chrominance_values[n]); } writeWord(0xFFC4); // marker writeWord(181); // length writeByte(0x11); // HTUACinfo for (let o = 0; o < 16; o++) { writeByte(std_ac_chrominance_nrcodes[o + 1]); } for (let p = 0; p <= 161; p++) { writeByte(std_ac_chrominance_values[p]); } } } function writeSOS(self: f5stego) { if (!self.#frame) throw "Frame not ready"; writeWord(0xFFDA); // marker writeWord(6 + self.#frame.components.length * 2); // length writeByte(self.#frame.components.length); // nrofcomponents for (let i = 0; i < self.#frame.components.length; i++) { const c = self.#frame.components[i]; writeByte(c.componentId); if (i === 0) { writeByte(0); } else { writeByte(0x11); } } writeByte(0); // Ss writeByte(0x3f); // Se writeByte(0); // Bf } function processDU(comp: FrameComponentType, POS: number, DC: number, HTDC: number[][], HTAC: number[][]) { let pos, posval, t; if (bytepos === 0) bytenew = 0; if (!('blocks' in comp)) throw "Blocks not parsed"; const Diff = comp.blocksDC[POS >> 6] - DC; DC = comp.blocksDC[POS >> 6]; //Encode DC if (Diff === 0) { posval = HTDC[0][1]; bytenew <<= posval; bytenew += HTDC[0][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } } else { pos = 32767 + Diff; posval = HTDC[category[pos]][1]; bytenew <<= posval; bytenew += HTDC[category[pos]][0]; bytepos += posval; posval = bitcode[pos][1]; bytenew <<= posval; bytenew += bitcode[pos][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } } //Encode ACs let end0pos = 63; // was const... which is crazy for (; (end0pos > 0) && (comp.blocks[POS + end0pos] === 0); end0pos--) { } //end0pos = first element in reverse order !=0 if (end0pos === 0) { posval = HTAC[0x00][1]; bytenew <<= posval; bytenew += HTAC[0x00][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } return DC; } let i = 1; let lng; while (i <= end0pos) { const startpos = i; for (; (comp.blocks[POS + i] === 0) && (i <= end0pos); ++i) { } let nrzeroes = i - startpos; if (nrzeroes >= 16) { lng = nrzeroes >> 4; for (let nrmarker = 1; nrmarker <= lng; ++nrmarker) { posval = HTAC[0xF0][1]; bytenew <<= posval; bytenew += HTAC[0xF0][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } } nrzeroes = nrzeroes & 0xF; } pos = 32767 + comp.blocks[POS + i]; posval = HTAC[(nrzeroes << 4) + category[pos]][1]; bytenew <<= posval; bytenew += HTAC[(nrzeroes << 4) + category[pos]][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } posval = bitcode[pos][1]; bytenew <<= posval; bytenew += bitcode[pos][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } i++; } if (end0pos != 63) { posval = HTAC[0x00][1]; bytenew <<= posval; bytenew += HTAC[0x00][0]; bytepos += posval; while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; bytenew &= (1 << bytepos) - 1; } } if (outpos > poslast) { t = new Uint8Array(byteout.length * 2); t.set(byteout); byteout = t; poslast = t.length - 128; } return DC; } // Initialize bit writer byteout = new Uint8Array(65536); poslast = 65536 - 128; outpos = 0; bytenew = 0; bytepos = 0; // Add JPEG headers writeWord(0xFFD8); // SOI writeAPP0(this); writeAPPn(this); writeDQT(this); writeSOF0(this); writeDHT(this); writeSOS(this); bytenew = 0; bytepos = 0; if (!this.#frame) throw "Frame not ready"; let c, mcuRow, mcuCol, blockRow, blockCol, mcu, i, v, h; const DCdiff: number[] = []; for (i = 0; i < this.#frame.components.length; i++) { DCdiff.push(0); } for (mcu = 0; mcu < this.#frame.mcusPerLine! * this.#frame.mcusPerColumn!; mcu++) { mcuRow = (mcu / this.#frame.mcusPerLine!) | 0; mcuCol = mcu % this.#frame.mcusPerLine!; for (i = 0; i < this.#frame.components.length; i++) { c = this.#frame.components[i]; for (v = 0; v < c.v; v++) { blockRow = mcuRow * c.v + v; for (h = 0; h < c.h; h++) { blockCol = mcuCol * c.h + h; if (i === 0) { DCdiff[i] = processDU(c, (blockRow * this.#frame.mcusPerLine! * c.h + blockCol) * 64, DCdiff[i], YDC_HT, YAC_HT); } else { DCdiff[i] = processDU(c, (blockRow * this.#frame.mcusPerLine! * c.h + blockCol) * 64, DCdiff[i], UVDC_HT, UVAC_HT); } } } } } // Write last bytes from coder while (bytepos > 7) { byte = 0xFF & (bytenew >>> (bytepos - 8)); byteout[outpos++] = byte; if (byte == 0xFF) { outpos++; } bytepos -= 8; } // And do the bit alignment of the EOI marker if (bytepos > 0) { bytenew <<= 8 - bytepos; bytenew += (1 << (8 - bytepos)) - 1; byteout[outpos++] = 0xFF & bytenew; } writeWord(0xFFD9); //EOI if (this.#tail) writeBlock(this.#tail); return byteout.slice(0, outpos); } }