mirror of
https://github.com/Sternenlabor/fabaccess-sticker-generator.git
synced 2025-03-12 23:11:49 +01:00
189 lines
6.0 KiB
JavaScript
189 lines
6.0 KiB
JavaScript
// Copyright (c) 2018 Shutterstock, Inc.
|
|
// The MIT License (MIT)
|
|
// https://github.com/shutterstock/changeDPI
|
|
|
|
function createPngDataTable() {
|
|
/* Table of CRCs of all 8-bit messages. */
|
|
const crcTable = new Int32Array(256);
|
|
for (let n = 0; n < 256; n++) {
|
|
let c = n;
|
|
for (let k = 0; k < 8; k++) {
|
|
c = (c & 1) ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
}
|
|
crcTable[n] = c;
|
|
}
|
|
return crcTable;
|
|
}
|
|
|
|
function calcCrc(buf) {
|
|
let c = -1;
|
|
if (!pngDataTable) pngDataTable = createPngDataTable();
|
|
for (let n = 0; n < buf.length; n++) {
|
|
c = pngDataTable[(c ^ buf[n]) & 0xFF] ^ (c >>> 8);
|
|
}
|
|
return c ^ -1;
|
|
}
|
|
|
|
let pngDataTable;
|
|
|
|
const PNG = 'image/png';
|
|
const JPEG = 'image/jpeg';
|
|
|
|
// those are 3 possible signature of the physBlock in base64.
|
|
// the pHYs signature block is preceed by the 4 bytes of lenght. The length of
|
|
// the block is always 9 bytes. So a phys block has always this signature:
|
|
// 0 0 0 9 p H Y s.
|
|
// However the data64 encoding aligns we will always find one of those 3 strings.
|
|
// this allow us to find this particular occurence of the pHYs block without
|
|
// converting from b64 back to string
|
|
const b64PhysSignature1 = 'AAlwSFlz';
|
|
const b64PhysSignature2 = 'AAAJcEhZ';
|
|
const b64PhysSignature3 = 'AAAACXBI';
|
|
|
|
const _P = 'p'.charCodeAt(0);
|
|
const _H = 'H'.charCodeAt(0);
|
|
const _Y = 'Y'.charCodeAt(0);
|
|
const _S = 's'.charCodeAt(0);
|
|
|
|
export function changeDpiBlob(blob, dpi) {
|
|
// 33 bytes are ok for pngs and jpegs
|
|
// to contain the information.
|
|
const headerChunk = blob.slice(0, 33);
|
|
return new Promise((resolve, reject) => {
|
|
const fileReader = new FileReader();
|
|
fileReader.onload = () => {
|
|
const dataArray = new Uint8Array(fileReader.result);
|
|
const tail = blob.slice(33);
|
|
const changedArray = changeDpiOnArray(dataArray, dpi, blob.type);
|
|
resolve(new Blob([changedArray, tail], { type: blob.type }));
|
|
};
|
|
fileReader.readAsArrayBuffer(headerChunk);
|
|
});
|
|
}
|
|
|
|
export function changeDpiDataUrl(base64Image, dpi) {
|
|
const dataSplitted = base64Image.split(',');
|
|
const format = dataSplitted[0];
|
|
const body = dataSplitted[1];
|
|
let type;
|
|
let headerLength;
|
|
let overwritepHYs = false;
|
|
if (format.indexOf(PNG) !== -1) {
|
|
type = PNG;
|
|
const b64Index = detectPhysChunkFromDataUrl(body);
|
|
// 28 bytes in dataUrl are 21bytes, length of phys chunk with everything inside.
|
|
if (b64Index >= 0) {
|
|
headerLength = Math.ceil((b64Index + 28) / 3) * 4;
|
|
overwritepHYs = true;
|
|
} else {
|
|
headerLength = 33 / 3 * 4;
|
|
}
|
|
}
|
|
if (format.indexOf(JPEG) !== -1) {
|
|
type = JPEG;
|
|
headerLength = 18 / 3 * 4;
|
|
}
|
|
// 33 bytes are ok for pngs and jpegs
|
|
// to contain the information.
|
|
const stringHeader = body.substring(0, headerLength);
|
|
const restOfData = body.substring(headerLength);
|
|
const headerBytes = atob(stringHeader);
|
|
const dataArray = new Uint8Array(headerBytes.length);
|
|
for (let i = 0; i < dataArray.length; i++) {
|
|
dataArray[i] = headerBytes.charCodeAt(i);
|
|
}
|
|
const finalArray = changeDpiOnArray(dataArray, dpi, type, overwritepHYs);
|
|
const base64Header = btoa(String.fromCharCode(...finalArray));
|
|
return [format, ',', base64Header, restOfData].join('');
|
|
}
|
|
|
|
function detectPhysChunkFromDataUrl(data) {
|
|
let b64index = data.indexOf(b64PhysSignature1);
|
|
if (b64index === -1) {
|
|
b64index = data.indexOf(b64PhysSignature2);
|
|
}
|
|
if (b64index === -1) {
|
|
b64index = data.indexOf(b64PhysSignature3);
|
|
}
|
|
// if b64index === -1 chunk is not found
|
|
return b64index;
|
|
}
|
|
|
|
function searchStartOfPhys(data) {
|
|
const length = data.length - 1;
|
|
// we check from the end since we cut the string in proximity of the header
|
|
// the header is within 21 bytes from the end.
|
|
for (let i = length; i >= 4; i--) {
|
|
if (data[i - 4] === 9 && data[i - 3] === _P &&
|
|
data[i - 2] === _H && data[i - 1] === _Y &&
|
|
data[i] === _S) {
|
|
return i - 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
function changeDpiOnArray(dataArray, dpi, format, overwritepHYs) {
|
|
if (format === JPEG) {
|
|
dataArray[13] = 1; // 1 pixel per inch or 2 pixel per cm
|
|
dataArray[14] = dpi >> 8; // dpiX high byte
|
|
dataArray[15] = dpi & 0xff; // dpiX low byte
|
|
dataArray[16] = dpi >> 8; // dpiY high byte
|
|
dataArray[17] = dpi & 0xff; // dpiY low byte
|
|
return dataArray;
|
|
}
|
|
if (format === PNG) {
|
|
const physChunk = new Uint8Array(13);
|
|
// chunk header pHYs
|
|
// 9 bytes of data
|
|
// 4 bytes of crc
|
|
// this multiplication is because the standard is dpi per meter.
|
|
dpi *= 39.3701;
|
|
physChunk[0] = _P;
|
|
physChunk[1] = _H;
|
|
physChunk[2] = _Y;
|
|
physChunk[3] = _S;
|
|
physChunk[4] = dpi >>> 24; // dpiX highest byte
|
|
physChunk[5] = dpi >>> 16; // dpiX veryhigh byte
|
|
physChunk[6] = dpi >>> 8; // dpiX high byte
|
|
physChunk[7] = dpi & 0xff; // dpiX low byte
|
|
physChunk[8] = physChunk[4]; // dpiY highest byte
|
|
physChunk[9] = physChunk[5]; // dpiY veryhigh byte
|
|
physChunk[10] = physChunk[6]; // dpiY high byte
|
|
physChunk[11] = physChunk[7]; // dpiY low byte
|
|
physChunk[12] = 1; // dot per meter....
|
|
|
|
const crc = calcCrc(physChunk);
|
|
|
|
const crcChunk = new Uint8Array(4);
|
|
crcChunk[0] = crc >>> 24;
|
|
crcChunk[1] = crc >>> 16;
|
|
crcChunk[2] = crc >>> 8;
|
|
crcChunk[3] = crc & 0xff;
|
|
|
|
if (overwritepHYs) {
|
|
const startingIndex = searchStartOfPhys(dataArray);
|
|
dataArray.set(physChunk, startingIndex);
|
|
dataArray.set(crcChunk, startingIndex + 13);
|
|
return dataArray;
|
|
} else {
|
|
// i need to give back an array of data that is divisible by 3 so that
|
|
// dataurl encoding gives me integers, for luck this chunk is 17 + 4 = 21
|
|
// if it was we could add a text chunk contaning some info, untill desired
|
|
// length is met.
|
|
|
|
// chunk structur 4 bytes for length is 9
|
|
const chunkLength = new Uint8Array(4);
|
|
chunkLength[0] = 0;
|
|
chunkLength[1] = 0;
|
|
chunkLength[2] = 0;
|
|
chunkLength[3] = 9;
|
|
|
|
const finalHeader = new Uint8Array(54);
|
|
finalHeader.set(dataArray, 0);
|
|
finalHeader.set(chunkLength, 33);
|
|
finalHeader.set(physChunk, 37);
|
|
finalHeader.set(crcChunk, 50);
|
|
return finalHeader;
|
|
}
|
|
}
|
|
} |