Comparing version 0.3.7 to 0.4.0
export interface RawImageData<T> { | ||
width: number, | ||
height: number, | ||
data: T | ||
width: number; | ||
height: number; | ||
data: T; | ||
} | ||
@@ -15,15 +15,23 @@ | ||
/** | ||
* @deprecated - decode takes an object since 0.3.5 | ||
*/ | ||
export declare function decode(jpegData: BufferLike, opts: true): UintArrRet; | ||
export declare function decode(jpegData: BufferLike, opts?: false): BufferRet; | ||
export declare function decode(jpegData: BufferLike, opts: { | ||
useTArray: true, | ||
colorTransform?: boolean | ||
}): UintArrRet; | ||
export declare function decode(jpegData: BufferLike, opts?: { | ||
useTArray?: false, | ||
colorTransform?: boolean | ||
}): BufferRet; | ||
export declare function decode( | ||
jpegData: BufferLike, | ||
opts: { | ||
useTArray: true; | ||
colorTransform?: boolean; | ||
formatAsRGBA?: boolean; | ||
tolerantDecoding?: boolean; | ||
maxResolutionInMP?: number; | ||
maxMemoryUsageInMB?: number; | ||
}, | ||
): UintArrRet; | ||
export declare function decode( | ||
jpegData: BufferLike, | ||
opts?: { | ||
useTArray?: false; | ||
colorTransform?: boolean; | ||
formatAsRGBA?: boolean; | ||
tolerantDecoding?: boolean; | ||
maxResolutionInMP?: number; | ||
maxMemoryUsageInMB?: number; | ||
}, | ||
): BufferRet; |
@@ -96,3 +96,3 @@ /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / | ||
spectralStart, spectralEnd, | ||
successivePrev, successive) { | ||
successivePrev, successive, opts) { | ||
var precision = frame.precision; | ||
@@ -262,2 +262,5 @@ var samplesPerLine = frame.samplesPerLine; | ||
var blockCol = mcuCol * component.h + col; | ||
// If the block is missing and we're in tolerant mode, just skip it. | ||
if (component.blocks[blockRow] === undefined && opts.tolerantDecoding) | ||
return; | ||
decode(component, component.blocks[blockRow][blockCol]); | ||
@@ -268,2 +271,5 @@ } | ||
var blockCol = mcu % component.blocksPerLine; | ||
// If the block is missing and we're in tolerant mode, just skip it. | ||
if (component.blocks[blockRow] === undefined && opts.tolerantDecoding) | ||
return; | ||
decode(component, component.blocks[blockRow][blockCol]); | ||
@@ -325,2 +331,14 @@ } | ||
if (mcu === mcuExpected) { | ||
// Skip trailing bytes at the end of the scan - until we reach the next marker | ||
do { | ||
if (data[offset] === 0xFF) { | ||
if (data[offset + 1] !== 0x00) { | ||
break; | ||
} | ||
} | ||
offset += 1; | ||
} while (offset < data.length - 2); | ||
} | ||
// find marker | ||
@@ -348,2 +366,3 @@ bitsCount = 0; | ||
var samplesPerLine = blocksPerLine << 3; | ||
// Only 1 used per invocation of this function and garbage collected after invocation, so no need to account for its memory footprint. | ||
var R = new Int32Array(64), r = new Uint8Array(64); | ||
@@ -511,2 +530,4 @@ | ||
requestMemoryAllocation(samplesPerLine * blocksPerColumn * 8); | ||
var i, j; | ||
@@ -550,2 +571,3 @@ for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { | ||
parse: function parse(data) { | ||
var maxResolutionInPixels = this.opts.maxResolutionInMP * 1000 * 1000; | ||
var offset = 0, length = data.length; | ||
@@ -582,3 +604,8 @@ function readUint16() { | ||
var blocksPerColumnForMcu = mcusPerColumn * component.v; | ||
var blocksToAllocate = blocksPerColumnForMcu * blocksPerLineForMcu; | ||
var blocks = []; | ||
// Each block is a Int32Array of length 64 (4 x 64 = 256 bytes) | ||
requestMemoryAllocation(blocksToAllocate * 256); | ||
for (var i = 0; i < blocksPerColumnForMcu; i++) { | ||
@@ -650,2 +677,12 @@ var row = []; | ||
// TODO APP1 - Exif | ||
if (fileMarker === 0xFFE1) { | ||
if (appData[0] === 0x45 && | ||
appData[1] === 0x78 && | ||
appData[2] === 0x69 && | ||
appData[3] === 0x66 && | ||
appData[4] === 0) { // 'EXIF\x00' | ||
this.exifBuffer = appData.subarray(5, appData.length); | ||
} | ||
} | ||
if (fileMarker === 0xFFEE) { | ||
@@ -669,2 +706,3 @@ if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && | ||
var quantizationTableSpec = data[offset++]; | ||
requestMemoryAllocation(64 * 4); | ||
var tableData = new Int32Array(64); | ||
@@ -699,2 +737,9 @@ if ((quantizationTableSpec >> 4) === 0) { // 8 bit values | ||
frame.componentsOrder = []; | ||
var pixelsInFrame = frame.scanLines * frame.samplesPerLine; | ||
if (pixelsInFrame > maxResolutionInPixels) { | ||
var exceededAmount = Math.ceil((pixelsInFrame - maxResolutionInPixels) / 1e6); | ||
throw new Error(`maxResolutionInMP limit exceeded by ${exceededAmount}MP`); | ||
} | ||
var componentsCount = data[offset++], componentId; | ||
@@ -725,4 +770,6 @@ var maxH = 0, maxV = 0; | ||
var codeLengthSum = 0; | ||
for (j = 0; j < 16; j++, offset++) | ||
for (j = 0; j < 16; j++, offset++) { | ||
codeLengthSum += (codeLengths[j] = data[offset]); | ||
} | ||
requestMemoryAllocation(16 + codeLengthSum); | ||
var huffmanValues = new Uint8Array(codeLengthSum); | ||
@@ -761,3 +808,3 @@ for (j = 0; j < codeLengthSum; j++, offset++) | ||
spectralStart, spectralEnd, | ||
successiveApproximation >> 4, successiveApproximation & 15); | ||
successiveApproximation >> 4, successiveApproximation & 15, this.opts); | ||
offset += processed; | ||
@@ -820,2 +867,3 @@ break; | ||
var dataLength = width * height * this.components.length; | ||
requestMemoryAllocation(dataLength); | ||
var data = new Uint8Array(dataLength); | ||
@@ -855,4 +903,4 @@ switch (this.components.length) { | ||
colorTransform = true; | ||
else if (typeof this.colorTransform !== 'undefined') | ||
colorTransform = !!this.colorTransform; | ||
else if (typeof this.opts.colorTransform !== 'undefined') | ||
colorTransform = !!this.opts.colorTransform; | ||
@@ -895,4 +943,4 @@ component1 = this.components[0]; | ||
colorTransform = true; | ||
else if (typeof this.colorTransform !== 'undefined') | ||
colorTransform = !!this.colorTransform; | ||
else if (typeof this.opts.colorTransform !== 'undefined') | ||
colorTransform = !!this.opts.colorTransform; | ||
@@ -1000,36 +1048,56 @@ component1 = this.components[0]; | ||
// We cap the amount of memory used by jpeg-js to avoid unexpected OOMs from untrusted content. | ||
var totalBytesAllocated = 0; | ||
var maxMemoryUsageBytes = 0; | ||
function requestMemoryAllocation(increaseAmount = 0) { | ||
var totalMemoryImpactBytes = totalBytesAllocated + increaseAmount; | ||
if (totalMemoryImpactBytes > maxMemoryUsageBytes) { | ||
var exceededAmount = Math.ceil((totalMemoryImpactBytes - maxMemoryUsageBytes) / 1024 / 1024); | ||
throw new Error(`maxMemoryUsageInMB limit exceeded by at least ${exceededAmount}MB`); | ||
} | ||
totalBytesAllocated = totalMemoryImpactBytes; | ||
} | ||
constructor.resetMaxMemoryUsage = function (maxMemoryUsageBytes_) { | ||
totalBytesAllocated = 0; | ||
maxMemoryUsageBytes = maxMemoryUsageBytes_; | ||
}; | ||
constructor.getBytesAllocated = function () { | ||
return totalBytesAllocated; | ||
}; | ||
constructor.requestMemoryAllocation = requestMemoryAllocation; | ||
return constructor; | ||
})(); | ||
module.exports = decode; | ||
function decode(jpegData, opts) { | ||
if (typeof module !== 'undefined') { | ||
module.exports = decode; | ||
} else if (typeof window !== 'undefined') { | ||
window['jpeg-js'] = window['jpeg-js'] || {}; | ||
window['jpeg-js'].decode = decode; | ||
} | ||
function decode(jpegData, userOpts = {}) { | ||
var defaultOpts = { | ||
useTArray: false, | ||
// "undefined" means "Choose whether to transform colors based on the image’s color model." | ||
colorTransform: undefined, | ||
formatAsRGBA: true | ||
useTArray: false, | ||
formatAsRGBA: true, | ||
tolerantDecoding: true, | ||
maxResolutionInMP: 100, // Don't decode more than 100 megapixels | ||
maxMemoryUsageInMB: 512, // Don't decode if memory footprint is more than 512MB | ||
}; | ||
if (opts) { | ||
if (typeof opts === 'object') { | ||
opts = { | ||
useTArray: (typeof opts.useTArray === 'undefined' ? | ||
defaultOpts.useTArray : opts.useTArray), | ||
colorTransform: (typeof opts.colorTransform === 'undefined' ? | ||
defaultOpts.colorTransform : opts.colorTransform), | ||
formatAsRGBA: (typeof opts.formatAsRGBA === 'undefined' ? | ||
defaultOpts.formatAsRGBA : opts.formatAsRGBA) | ||
}; | ||
} else { | ||
// backwards compatiblity, before 0.3.5, we only had the useTArray param | ||
opts = defaultOpts; | ||
opts.useTArray = true; | ||
} | ||
} else { | ||
opts = defaultOpts; | ||
} | ||
var opts = {...defaultOpts, ...userOpts}; | ||
var arr = new Uint8Array(jpegData); | ||
var decoder = new JpegImage(); | ||
decoder.opts = opts; | ||
// If this constructor ever supports async decoding this will need to be done differently. | ||
// Until then, treating as singleton limit is fine. | ||
JpegImage.resetMaxMemoryUsage(opts.maxMemoryUsageInMB * 1024 * 1024); | ||
decoder.parse(arr); | ||
decoder.colorTransform = opts.colorTransform; | ||
@@ -1039,5 +1107,7 @@ var channels = (opts.formatAsRGBA) ? 4 : 3; | ||
try { | ||
JpegImage.requestMemoryAllocation(bytesNeeded); | ||
var image = { | ||
width: decoder.width, | ||
height: decoder.height, | ||
exifBuffer: decoder.exifBuffer, | ||
data: opts.useTArray ? | ||
@@ -1044,0 +1114,0 @@ new Uint8Array(bytesNeeded) : |
@@ -441,3 +441,29 @@ /* | ||
} | ||
function writeAPP1(exifBuffer) { | ||
if (!exifBuffer) return; | ||
writeWord(0xFFE1); // APP1 marker | ||
if (exifBuffer[0] === 0x45 && | ||
exifBuffer[1] === 0x78 && | ||
exifBuffer[2] === 0x69 && | ||
exifBuffer[3] === 0x66) { | ||
// Buffer already starts with EXIF, just use it directly | ||
writeWord(exifBuffer.length + 2); // length is buffer + length itself! | ||
} else { | ||
// Buffer doesn't start with EXIF, write it for them | ||
writeWord(exifBuffer.length + 5 + 2); // length is buffer + EXIF\0 + length itself! | ||
writeByte(0x45); // E | ||
writeByte(0x78); // X | ||
writeByte(0x69); // I | ||
writeByte(0x66); // F | ||
writeByte(0); // = "EXIF",'\0' | ||
} | ||
for (var i = 0; i < exifBuffer.length; i++) { | ||
writeByte(exifBuffer[i]); | ||
} | ||
} | ||
function writeSOF0(width, height) | ||
@@ -603,2 +629,3 @@ { | ||
writeAPP0(); | ||
writeAPP1(image.exifBuffer); | ||
writeDQT(); | ||
@@ -691,3 +718,3 @@ writeSOF0(image.width,image.height); | ||
//return new Uint8Array(byteout); | ||
if (typeof module === 'undefined') return new Uint8Array(byteout); | ||
return new Buffer(byteout); | ||
@@ -746,4 +773,8 @@ | ||
}; | ||
if (typeof module !== undefined) { | ||
if (typeof module !== 'undefined') { | ||
module.exports = encode; | ||
} else if (typeof window !== 'undefined') { | ||
window['jpeg-js'] = window['jpeg-js'] || {}; | ||
window['jpeg-js'].encode = encode; | ||
} | ||
@@ -750,0 +781,0 @@ |
{ | ||
"name": "jpeg-js", | ||
"version": "0.3.7", | ||
"version": "0.4.0", | ||
"description": "A pure javascript JPEG encoder and decoder", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node_modules/.bin/tape test/*.js" | ||
"test": "jest --testMatch=**/test/*.js" | ||
}, | ||
@@ -30,5 +30,4 @@ "repository": { | ||
"devDependencies": { | ||
"redtape": "~0.1.0", | ||
"tape": "~2.3.2" | ||
"jest": "^25.4.0" | ||
} | ||
} | ||
} |
@@ -5,2 +5,4 @@ # jpeg-js | ||
**NOTE:** this is a _synchronous_ (i.e. CPU-blocking) library that is much slower than native alternatives. If you don't need a _pure javascript_ implementation, consider using async alternatives like [sharp](http://npmjs.com/package/sharp) in node or the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) in the browser. | ||
[![build status](https://secure.travis-ci.org/eugeneware/jpeg-js.png)](http://travis-ci.org/eugeneware/jpeg-js) | ||
@@ -12,3 +14,3 @@ | ||
``` bash | ||
```bash | ||
$ npm install jpeg-js | ||
@@ -23,3 +25,3 @@ ``` | ||
``` js | ||
```js | ||
var jpeg = require('jpeg-js'); | ||
@@ -36,9 +38,9 @@ var jpegData = fs.readFileSync('grumpycat.jpg'); | ||
To decode directly into a `Uint8Array`, pass `true` as the second argument to | ||
To decode directly into a `Uint8Array`, pass `useTArray: true` in options | ||
`decode`: | ||
``` js | ||
```js | ||
var jpeg = require('jpeg-js'); | ||
var jpegData = fs.readFileSync('grumpycat.jpg'); | ||
var rawImageData = jpeg.decode(jpegData, true); // return as Uint8Array | ||
var rawImageData = jpeg.decode(jpegData, {useTArray: true}); // return as Uint8Array | ||
console.log(rawImageData); | ||
@@ -52,14 +54,26 @@ /* | ||
#### Decode Options | ||
| Option | Description | Default | | ||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | | ||
| `colorTransform` | Transform alternate colorspaces like YCbCr. `undefined` means respect the default behavior encoded in metadata. | `undefined` | | ||
| `useTArray` | Decode pixels into a typed `Uint8Array` instead of a `Buffer`. | `false` | | ||
| `formatAsRGBA` | Decode pixels into RGBA vs. RGB. | `true` | | ||
| `tolerantDecoding` | Be more tolerant when encountering technically invalid JPEGs. | `true` | | ||
| `maxResolutionInMP` | The maximum resolution image that `jpeg-js` should attempt to decode in megapixels. Images larger than this resolution will throw an error instead of decoding. | `100` | | ||
| `maxMemoryUsageInMB` | The (approximate) maximum memory that `jpeg-js` should allocate while attempting to decode the image in mebibyte. Images requiring more memory than this will throw an error instead of decoding. | `512` | | ||
### Encoding JPEGs | ||
``` js | ||
```js | ||
var jpeg = require('jpeg-js'); | ||
var width = 320, height = 180; | ||
var width = 320, | ||
height = 180; | ||
var frameData = new Buffer(width * height * 4); | ||
var i = 0; | ||
while (i < frameData.length) { | ||
frameData[i++] = 0xFF; // red | ||
frameData[i++] = 0xff; // red | ||
frameData[i++] = 0x00; // green | ||
frameData[i++] = 0x00; // blue | ||
frameData[i++] = 0xFF; // alpha - ignored in JPEGs | ||
frameData[i++] = 0xff; // alpha - ignored in JPEGs | ||
} | ||
@@ -69,3 +83,3 @@ var rawImageData = { | ||
width: width, | ||
height: height | ||
height: height, | ||
}; | ||
@@ -80,3 +94,3 @@ var jpegImageData = jpeg.encode(rawImageData, 50); | ||
// write to file | ||
fs.writeFileSync("image.jpg", jpegImageData.data); | ||
fs.writeFileSync('image.jpg', jpegImageData.data); | ||
``` | ||
@@ -119,15 +133,15 @@ | ||
Redistribution and use in source and binary forms, with or without | ||
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, | ||
- 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 | ||
- 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 | ||
- 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. | ||
@@ -138,3 +152,3 @@ | ||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | ||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | ||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||
@@ -141,0 +155,0 @@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
221873
1
10
1766
177