dpi-tools
Version:
Quickly change the dpi for an image without re-enconding
291 lines (230 loc) • 8.86 kB
JavaScript
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var PNG = 'image/png';
var 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
var B64_PHYS_SIGNATURE_1 = 'AAlwSFlz';
var B64_PHYS_SIGNATURE_2 = 'AAAJcEhZ';
var B64_PHYS_SIGNATURE_3 = 'AAAACXBI';
var P_CHAR_CODE = 'p'.charCodeAt(0);
var H_CHAR_CODE = 'H'.charCodeAt(0);
var Y_CHAR_CODE = 'Y'.charCodeAt(0);
var S_CHAR_CODE = 's'.charCodeAt(0);
var createPngDataTable = function createPngDataTable() {
/* Table of CRCs of all 8-bit messages. */
var crcTable = new Int32Array(256);
for (var n = 0; n < 256; n += 1) {
var c = n;
for (var k = 0; k < 8; k += 1) {
c = c & 1 ? 0xedb88320 ^ c >>> 1 : c >>> 1;
}
crcTable[n] = c;
}
return crcTable;
};
var pngDataTable = createPngDataTable();
var calcCrc = function calcCrc(buf) {
var c = -1;
for (var n = 0; n < buf.length; n += 1) {
c = pngDataTable[(c ^ buf[n]) & 0xff] ^ c >>> 8;
}
return c ^ -1;
};
var searchStartOfPhys = function searchStartOfPhys(data) {
var 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 (var i = length; i >= 4; i -= 1) {
if (data[i - 4] === 9 && data[i - 3] === P_CHAR_CODE && data[i - 2] === H_CHAR_CODE && data[i - 1] === Y_CHAR_CODE && data[i] === S_CHAR_CODE) {
return i - 3;
}
}
return -1;
};
var changeDpiOnArray = function changeDpiOnArray(dataArray, sourceDpi, format) {
var overwritePhys = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var dpi = sourceDpi;
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) {
var 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_CHAR_CODE;
physChunk[1] = H_CHAR_CODE;
physChunk[2] = Y_CHAR_CODE;
physChunk[3] = S_CHAR_CODE;
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....
var crc = calcCrc(physChunk);
var crcChunk = new Uint8Array(4);
crcChunk[0] = crc >>> 24;
crcChunk[1] = crc >>> 16;
crcChunk[2] = crc >>> 8;
crcChunk[3] = crc & 0xff;
if (overwritePhys) {
var startingIndex = searchStartOfPhys(dataArray);
dataArray.set(physChunk, startingIndex);
dataArray.set(crcChunk, startingIndex + 13);
return dataArray;
} // 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 containing some info, until desired
// length is met.
// chunk structure 4 bytes for length is 9
var chunkLength = new Uint8Array(4);
chunkLength[0] = 0;
chunkLength[1] = 0;
chunkLength[2] = 0;
chunkLength[3] = 9;
var finalHeader = new Uint8Array(54);
finalHeader.set(dataArray, 0);
finalHeader.set(chunkLength, 33);
finalHeader.set(physChunk, 37);
finalHeader.set(crcChunk, 50);
return finalHeader;
}
return null;
};
var detectPhysChunkFromDataUrl = function detectPhysChunkFromDataUrl(data) {
var b64index = data.indexOf(B64_PHYS_SIGNATURE_1);
if (b64index === -1) {
b64index = data.indexOf(B64_PHYS_SIGNATURE_2);
}
if (b64index === -1) {
b64index = data.indexOf(B64_PHYS_SIGNATURE_3);
} // if b64index === -1 chunk is not found
return b64index;
};
var isBufferPng = function isBufferPng(buffer) {
if (buffer.length < 8) {
return null;
}
return buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10;
};
var isBufferJpeg = function isBufferJpeg(buffer) {
if (buffer.length < 3) {
return null;
}
return buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255;
};
var getType = function getType(buffer) {
if (isBufferJpeg(buffer)) {
return JPEG;
}
if (isBufferPng(buffer)) {
return PNG;
}
return '';
};
var mergeTypedArrays = function mergeTypedArrays(a, b) {
var c = new a.constructor(a.length + b.length);
c.set(a);
c.set(b, a.length);
return c;
};
var changeDpiDataUrl = function changeDpiDataUrl(base64Image, dpi) {
var dataSplitted = base64Image.split(',');
var format = dataSplitted[0];
var body = dataSplitted[1];
var type;
var headerLength;
var overwritePhys = false;
if (format.indexOf(PNG) !== -1) {
type = PNG;
var 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;
}
} else if (format.indexOf(JPEG) !== -1) {
type = JPEG;
headerLength = 18 / 3 * 4;
} // 33 bytes are ok for pngs and jpegs
// to contain the information.
var stringHeader = body.substring(0, headerLength);
var restOfData = body.substring(headerLength);
var headerBytes = atob(stringHeader);
var dataArray = new Uint8Array(headerBytes.length);
for (var i = 0; i < dataArray.length; i += 1) {
dataArray[i] = headerBytes.charCodeAt(i);
}
var finalArray = changeDpiOnArray(dataArray, dpi, type, overwritePhys);
var base64Header = btoa(String.fromCharCode.apply(String, _toConsumableArray(finalArray)));
return [format, ',', base64Header, restOfData].join('');
};
var changeDpiBlob = function changeDpiBlob(blob, dpi) {
// 33 bytes are ok for pngs and jpegs
// to contain the information.
var headerChunk = blob.slice(0, 33);
return new Promise(function (resolve) {
var fileReader = new FileReader();
fileReader.onload = function () {
var dataArray = new Uint8Array(fileReader.result);
var tail = blob.slice(33);
var changedArray = changeDpiOnArray(dataArray, dpi, blob.type);
resolve(new Blob([changedArray, tail], {
type: blob.type
}));
};
fileReader.readAsArrayBuffer(headerChunk);
});
};
var changeDpiBuffer = function changeDpiBuffer(buffer, dpi) {
var headerChunk = new Uint8Array(buffer.slice(0, 33));
var rest = buffer.slice(33);
var type = getType(buffer);
var changedHeader = changeDpiOnArray(headerChunk, dpi, type);
return mergeTypedArrays(changedHeader, rest);
};
var dpiTools = {
changeDpiDataUrl: changeDpiDataUrl,
changeDpiBlob: changeDpiBlob,
changeDpiBuffer: changeDpiBuffer
};
export default dpiTools;
export { changeDpiBlob, changeDpiBuffer, changeDpiDataUrl };