You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1663 lines
44 KiB
1663 lines
44 KiB
/* 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, K extends keyof T, V extends T[K]> = T extends Record<K, V> ? T : never;
|
|
|
|
type FrameType = {
|
|
extended: boolean,
|
|
progressive: boolean,
|
|
precision: number,
|
|
scanLines: number,
|
|
samplesPerLine: number,
|
|
components: (FrameComponentType)[];
|
|
mcusPerLine?: number,
|
|
mcusPerColumn?: number,
|
|
componentIds: Record<number, number>,
|
|
maxH: number,
|
|
maxV: number
|
|
};
|
|
|
|
export class f5stego {
|
|
constructor(key: ArrayLike<number>, private maxPixels: number = 4096 * 4096) {
|
|
}
|
|
|
|
embed(image: Uint8Array, data: ArrayLike<number>, 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<number>, 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<number>, 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<void, Buffer, number>;
|
|
|
|
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<number>,
|
|
offset: number,
|
|
frame: FrameType,
|
|
components: DiscriminateUnion<FrameComponentType, 'huffmanTableDC', Uint16Array>[],
|
|
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);
|
|
}
|
|
|
|
}
|
|
|