ohif-viewer/Packages/cornerstone/client/cornerstoneWADOImageLoader.js

4202 lines
132 KiB
JavaScript

/*! cornerstone-wado-image-loader - v0.7.2 - 2015-09-18 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneWADOImageLoader */
//
// This is a cornerstone image loader for WADO-URI requests. It has limited support for compressed
// transfer syntaxes, check here to see what is currently supported:
//
// https://github.com/chafey/cornerstoneWADOImageLoader/blob/master/docs/TransferSyntaxes.md
//
// It will support implicit little endian transfer syntaxes but explicit little endian is strongly preferred
// to avoid any parsing issues related to SQ elements. To request that the WADO object be returned as explicit little endian, append
// the following on your WADO url: &transferSyntax=1.2.840.10008.1.2.1
//
if(typeof cornerstone === 'undefined'){
cornerstone = {};
}
if(typeof cornerstoneWADOImageLoader === 'undefined'){
cornerstoneWADOImageLoader = {
internal: {
options : {
// callback allowing customization of the xhr (e.g. adding custom auth headers, cors, etc)
beforeSend: function (xhr) {
}
},
multiFrameCacheHack : {}
}
};
}
(function (cornerstoneWADOImageLoader) {
"use strict";
function convertRGB(dataSet, decodedImageFrame, rgbaBuffer) {
var planarConfiguration = dataSet.uint16('x00280006');
if(planarConfiguration === 0) {
cornerstoneWADOImageLoader.convertRGBColorByPixel(decodedImageFrame, rgbaBuffer);
} else {
cornerstoneWADOImageLoader.convertRGBColorByPlane(decodedImageFrame, rgbaBuffer);
}
}
function convertColorSpace(canvas, dataSet, imageFrame) {
// extract the fields we need
var height = dataSet.uint16('x00280010');
var width = dataSet.uint16('x00280011');
var photometricInterpretation = dataSet.string('x00280004');
// setup the canvas context
canvas.height = height;
canvas.width = width;
var context = canvas.getContext('2d');
var imageData = context.createImageData(width, height);
// convert based on the photometric interpretation
var deferred = $.Deferred();
try {
if (photometricInterpretation === "RGB" )
{
convertRGB(dataSet, imageFrame, imageData.data);
}
else if (photometricInterpretation === "YBR_RCT")
{
convertRGB(dataSet, imageFrame, imageData.data);
}
else if (photometricInterpretation === "YBR_ICT")
{
convertRGB(dataSet, imageFrame, imageData.data);
}
else if( photometricInterpretation === "PALETTE COLOR" )
{
cornerstoneWADOImageLoader.convertPALETTECOLOR(imageFrame, imageData.data, dataSet );
}
else if( photometricInterpretation === "YBR_FULL_422" )
{
cornerstoneWADOImageLoader.convertYBRFull(imageFrame, imageData.data);
}
else if(photometricInterpretation === "YBR_FULL" )
{
cornerstoneWADOImageLoader.convertYBRFull(imageFrame, imageData.data);
}
else
{
throw "no color space conversion for photometric interpretation " + photometricInterpretation;
}
deferred.resolve(imageData);
return deferred;
} catch (error) {
deferred.reject(error);
return deferred;
}
}
// module exports
cornerstoneWADOImageLoader.convertColorSpace = convertColorSpace;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function convertPALETTECOLOR( imageFrame, rgbaBuffer, dataSet ) {
var len=dataSet.int16('x00281101',0);
var start=dataSet.int16('x00281101',1);
var bits=dataSet.int16('x00281101',2);
var shift = (bits===8 ? 0 : 8 );
var buffer = dataSet.byteArray.buffer;
var rData=new Uint16Array( buffer, dataSet.elements.x00281201.dataOffset, len );
var gData=new Uint16Array( buffer, dataSet.elements.x00281202.dataOffset, len );
var bData=new Uint16Array( buffer, dataSet.elements.x00281203.dataOffset, len );
var numPixels = dataSet.uint16('x00280010') * dataSet.uint16('x00280011');
var palIndex=0;
var rgbaIndex=0;
for( var i=0 ; i < numPixels ; ++i ) {
var value=imageFrame[palIndex++];
if( value < start )
value=0;
else if( value > start + len -1 )
value=len-1;
else
value=value-start;
rgbaBuffer[ rgbaIndex++ ] = rData[value] >> shift;
rgbaBuffer[ rgbaIndex++ ] = gData[value] >> shift;
rgbaBuffer[ rgbaIndex++ ] = bData[value] >> shift;
rgbaBuffer[ rgbaIndex++ ] = 255;
}
}
// module exports
cornerstoneWADOImageLoader.convertPALETTECOLOR = convertPALETTECOLOR;
}(cornerstoneWADOImageLoader));
/**
*/
(function (cornerstoneWADOImageLoader) {
"use strict";
function convertRGBColorByPixel(imageFrame, rgbaBuffer) {
if(imageFrame === undefined) {
throw "decodeRGB: rgbBuffer must not be undefined";
}
if(imageFrame.length % 3 !== 0) {
throw "decodeRGB: rgbBuffer length must be divisible by 3";
}
var numPixels = imageFrame.length / 3;
var rgbIndex = 0;
var rgbaIndex = 0;
for(var i= 0; i < numPixels; i++) {
rgbaBuffer[rgbaIndex++] = imageFrame[rgbIndex++]; // red
rgbaBuffer[rgbaIndex++] = imageFrame[rgbIndex++]; // green
rgbaBuffer[rgbaIndex++] = imageFrame[rgbIndex++]; // blue
rgbaBuffer[rgbaIndex++] = 255; //alpha
}
}
// module exports
cornerstoneWADOImageLoader.convertRGBColorByPixel = convertRGBColorByPixel;
}(cornerstoneWADOImageLoader));
/**
*/
(function (cornerstoneWADOImageLoader) {
"use strict";
function convertRGBColorByPlane(imageFrame, rgbaBuffer) {
if(imageFrame === undefined) {
throw "decodeRGB: rgbBuffer must not be undefined";
}
if(imageFrame.length % 3 !== 0) {
throw "decodeRGB: rgbBuffer length must be divisible by 3";
}
var numPixels = imageFrame.length / 3;
var rgbaIndex = 0;
for(var i= 0; i < numPixels; i++) {
var rIndex = 0;
var gIndex = numPixels;
var bIndex = numPixels*2;
for(var i= 0; i < numPixels; i++) {
rgbaBuffer[rgbaIndex++] = imageFrame[rIndex++]; // red
rgbaBuffer[rgbaIndex++] = imageFrame[gIndex++]; // green
rgbaBuffer[rgbaIndex++] = imageFrame[bIndex++]; // blue
rgbaBuffer[rgbaIndex++] = 255; //alpha
}
}
}
// module exports
cornerstoneWADOImageLoader.convertRGBColorByPlane = convertRGBColorByPlane;
}(cornerstoneWADOImageLoader));
/**
*/
(function (cornerstoneWADOImageLoader) {
"use strict";
function convertYBRFull(imageFrame, rgbaBuffer) {
if(imageFrame === undefined) {
throw "decodeRGB: ybrBuffer must not be undefined";
}
if(imageFrame.length % 3 !== 0) {
throw "decodeRGB: ybrBuffer length must be divisble by 3";
}
var numPixels = imageFrame.length / 3;
var ybrIndex = 0;
var rgbaIndex = 0;
for(var i= 0; i < numPixels; i++) {
var y = imageFrame[ybrIndex++];
var cb = imageFrame[ybrIndex++];
var cr = imageFrame[ybrIndex++];
rgbaBuffer[rgbaIndex++] = y + 1.40200 * (cr - 128);// red
rgbaBuffer[rgbaIndex++] = y - 0.34414 * (cb -128) - 0.71414 * (cr- 128); // green
rgbaBuffer[rgbaIndex++] = y + 1.77200 * (cb - 128); // blue
rgbaBuffer[rgbaIndex++] = 255; //alpha
}
}
// module exports
cornerstoneWADOImageLoader.convertYBRFull = convertYBRFull;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function configure(options) {
cornerstoneWADOImageLoader.internal.options = options;
}
// module exports
cornerstoneWADOImageLoader.configure = configure;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function createImageObject( dataSet, imageId, frame ) {
if(frame === undefined) {
frame = 0;
}
// make the image based on whether it is color or not
var photometricInterpretation = dataSet.string('x00280004');
var isColor = cornerstoneWADOImageLoader.isColorImage(photometricInterpretation);
if(isColor === false) {
return cornerstoneWADOImageLoader.makeGrayscaleImage(imageId, dataSet, frame);
} else {
return cornerstoneWADOImageLoader.makeColorImage(imageId, dataSet, frame);
}
}
// module exports
cornerstoneWADOImageLoader.createImageObject = createImageObject;
}(cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function decodeJPEG2000(dataSet, frame)
{
var height = dataSet.uint16('x00280010');
var width = dataSet.uint16('x00280011');
var compressedPixelData = dicomParser.readEncapsulatedPixelData(dataSet, dataSet.elements.x7fe00010, frame);
var jpxImage = new JpxImage();
jpxImage.parse(compressedPixelData);
var j2kWidth = jpxImage.width;
var j2kHeight = jpxImage.height;
if(j2kWidth !== width) {
throw 'JPEG2000 decoder returned width of ' + j2kWidth + ', when ' + width + ' is expected';
}
if(j2kHeight !== height) {
throw 'JPEG2000 decoder returned width of ' + j2kHeight + ', when ' + height + ' is expected';
}
var tileCount = jpxImage.tiles.length;
if(tileCount !== 1) {
throw 'JPEG2000 decoder returned a tileCount of ' + tileCount + ', when 1 is expected';
}
var tileComponents = jpxImage.tiles[0];
var pixelData = tileComponents.items;
return pixelData;
}
cornerstoneWADOImageLoader.decodeJPEG2000 = decodeJPEG2000;
}($, cornerstone, cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function decodeJPEGBaseline(dataSet, frame)
{
var pixelDataElement = dataSet.elements.x7fe00010;
var height = dataSet.uint16('x00280010');
var width = dataSet.uint16('x00280011');
var bitsAllocated = dataSet.uint16('x00280100');
var frameData = dicomParser.readEncapsulatedPixelData(dataSet, pixelDataElement, frame);
var jpeg = new JpegImage();
jpeg.parse( frameData );
if(bitsAllocated === 8) {
return jpeg.getData(width, height);
}
else if(bitsAllocated === 16) {
return jpeg.getData16(width, height);
}
}
cornerstoneWADOImageLoader.decodeJPEGBaseline = decodeJPEGBaseline;
}($, cornerstone, cornerstoneWADOImageLoader));
/**
* Special decoder for 8 bit jpeg that leverages the browser's built in JPEG decoder for increased performance
*/
(function (cornerstoneWADOImageLoader) {
"use strict";
function arrayBufferToString(buffer) {
return binaryToString(String.fromCharCode.apply(null, Array.prototype.slice.apply(new Uint8Array(buffer))));
}
function binaryToString(binary) {
var error;
try {
return decodeURIComponent(escape(binary));
} catch (_error) {
error = _error;
if (error instanceof URIError) {
return binary;
} else {
throw error;
}
}
}
function decodeJPEGBaseline8Bit(canvas, dataSet, frame) {
var deferred = $.Deferred();
var height = dataSet.uint16('x00280010');
var width = dataSet.uint16('x00280011');
// resize the canvas
canvas.height = height;
canvas.width = width;
var encodedPixelData = dicomParser.readEncapsulatedPixelData(dataSet, dataSet.elements.x7fe00010, frame);
var imgBlob = new Blob([encodedPixelData], {type: "image/jpeg"});
var r = new FileReader();
if(r.readAsBinaryString === undefined) {
r.readAsArrayBuffer(imgBlob);
}
else {
r.readAsBinaryString(imgBlob); // doesn't work on IE11
}
r.onload = function(){
var img=new Image();
img.onload = function() {
var context = canvas.getContext('2d');
context.drawImage(this, 0, 0);
var imageData = context.getImageData(0, 0, width, height);
deferred.resolve(imageData);
};
img.onerror = function(error) {
deferred.reject(error);
};
if(r.readAsBinaryString === undefined) {
img.src = "data:image/jpeg;base64,"+window.btoa(arrayBufferToString(r.result));
}
else {
img.src = "data:image/jpeg;base64,"+window.btoa(r.result); // doesn't work on IE11
}
};
return deferred;
}
function isJPEGBaseline8Bit(dataSet) {
var transferSyntax = dataSet.string('x00020010');
var bitsAllocated = dataSet.uint16('x00280100');
if((bitsAllocated === 8) &&
transferSyntax === "1.2.840.10008.1.2.4.50")
{
return true;
}
}
// module exports
cornerstoneWADOImageLoader.decodeJPEGBaseline8Bit = decodeJPEGBaseline8Bit;
cornerstoneWADOImageLoader.isJPEGBaseline8Bit = isJPEGBaseline8Bit;
}(cornerstoneWADOImageLoader));
"use strict";
(function (cornerstoneWADOImageLoader) {
function decodeJPEGLossless(dataSet, frame) {
var pixelDataElement = dataSet.elements.x7fe00010;
var bitsAllocated = dataSet.uint16('x00280100');
var pixelRepresentation = dataSet.uint16('x00280103');
var frameData = dicomParser.readEncapsulatedPixelData(dataSet, pixelDataElement, frame);
var byteOutput = bitsAllocated <= 8 ? 1 : 2;
//console.time('jpeglossless');
var decoder = new jpeg.lossless.Decoder();
var decompressedData = decoder.decode(frameData.buffer, frameData.byteOffset, frameData.length, byteOutput);
//console.timeEnd('jpeglossless');
if (pixelRepresentation === 0) {
if (byteOutput === 2) {
return new Uint16Array(decompressedData.buffer);
} else {
// untested!
return new Uint8Array(decompressedData.buffer);
}
} else {
return new Int16Array(decompressedData.buffer);
}
}
// module exports
cornerstoneWADOImageLoader.decodeJPEGLossless = decodeJPEGLossless;
}(cornerstoneWADOImageLoader));
/**
*/
(function (cornerstoneWADOImageLoader) {
function decodeRLE(dataSet, frame) {
var height = dataSet.uint16('x00280010');
var width = dataSet.uint16('x00280011');
var samplesPerPixel = dataSet.uint16('x00280002');
var pixelDataElement = dataSet.elements.x7fe00010;
var frameData = dicomParser.readEncapsulatedPixelData(dataSet, pixelDataElement, frame);
var pixelFormat = cornerstoneWADOImageLoader.getPixelFormat(dataSet);
var frameSize = width*height;
var buffer;
if( pixelFormat===1 ) {
buffer = new ArrayBuffer(frameSize*samplesPerPixel);
decode8( frameData, buffer, frameSize, samplesPerPixel);
return new Uint8Array(buffer);
} else if( pixelFormat===2 ) {
buffer = new ArrayBuffer(frameSize*samplesPerPixel*2);
decode16( frameData, buffer, frameSize );
return new Uint16Array(buffer);
} else if( pixelFormat===3 ) {
buffer = new ArrayBuffer(frameSize*samplesPerPixel*2);
decode16( frameData, buffer, frameSize );
return new Int16Array(buffer);
}
}
function decode8( frameData, outFrame, frameSize, samplesSize ) {
var header=new DataView(frameData.buffer, frameData.byteOffset);
var data=new DataView( frameData.buffer, frameData.byteOffset );
var out=new DataView( outFrame );
var outIndex=0;
var numSegments = header.getInt32(0,true);
for( var s=0 ; s < numSegments ; ++s ) {
outIndex = s;
var inIndex=header.getInt32( (s+1)*4,true);
var maxIndex=header.getInt32( (s+2)*4,true);
if( maxIndex===0 )
maxIndex = frameData.length;
var endOfSegment = frameSize * numSegments;
while( inIndex < maxIndex ) {
var n=data.getInt8(inIndex++);
if( n >=0 && n <=127 ) {
// copy n bytes
for( var i=0 ; i < n+1 && outIndex < endOfSegment; ++i ) {
out.setInt8(outIndex, data.getInt8(inIndex++));
outIndex+=samplesSize;
}
} else if( n<= -1 && n>=-127 ) {
var value=data.getInt8(inIndex++);
// run of n bytes
for( var j=0 ; j < -n+1 && outIndex < endOfSegment; ++j ) {
out.setInt8(outIndex, value );
outIndex+=samplesSize;
}
} else if (n===-128)
; // do nothing
}
}
}
function decode16( frameData, outFrame, frameSize ) {
var header=new DataView(frameData.buffer, frameData.byteOffset);
var data=new DataView( frameData.buffer, frameData.byteOffset );
var out=new DataView( outFrame );
var numSegments = header.getInt32(0,true);
for( var s=0 ; s < numSegments ; ++s ) {
var outIndex=0;
var highByte=( s===0 ? 1 : 0);
var inIndex=header.getInt32( (s+1)*4,true);
var maxIndex=header.getInt32( (s+2)*4,true);
if( maxIndex===0 )
maxIndex = frameData.length;
while( inIndex < maxIndex ) {
var n=data.getInt8(inIndex++);
if( n >=0 && n <=127 ) {
for( var i=0 ; i < n+1 && outIndex < frameSize ; ++i ) {
out.setInt8( (outIndex*2)+highByte, data.getInt8(inIndex++) );
outIndex++;
}
} else if( n<= -1 && n>=-127 ) {
var value=data.getInt8(inIndex++);
for( var j=0 ; j < -n+1 && outIndex < frameSize ; ++j ) {
out.setInt8( (outIndex*2)+highByte, value );
outIndex++;
}
} else if (n===-128)
; // do nothing
}
}
}
// module exports
cornerstoneWADOImageLoader.decodeRLE = decodeRLE;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function decodeTransferSyntax(dataSet, frame) {
var transferSyntax = dataSet.string('x00020010');
// Implicit VR Little Endian
if( transferSyntax === "1.2.840.10008.1.2") {
return cornerstoneWADOImageLoader.extractUncompressedPixels(dataSet, frame);
}
// Explicit VR Little Endian
else if( transferSyntax === "1.2.840.10008.1.2.1") {
return cornerstoneWADOImageLoader.extractUncompressedPixels(dataSet, frame);
}
// JPEG 2000 Lossless
else if(transferSyntax === "1.2.840.10008.1.2.4.90")
{
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
}
// JPEG 2000 Lossy
else if(transferSyntax === "1.2.840.10008.1.2.4.91")
{
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
}
/* Don't know if these work...
// JPEG 2000 Part 2 Multicomponent Image Compression (Lossless Only)
else if(transferSyntax === "1.2.840.10008.1.2.4.92")
{
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
}
// JPEG 2000 Part 2 Multicomponent Image Compression
else if(transferSyntax === "1.2.840.10008.1.2.4.93")
{
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
}
*/
// RLE Lossless
else if ( transferSyntax === "1.2.840.10008.1.2.5" )
{
return cornerstoneWADOImageLoader.decodeRLE( dataSet, frame);
}
// JPEG Baseline lossy process 1 (8 bit)
else if ( transferSyntax === "1.2.840.10008.1.2.4.50" )
{
return cornerstoneWADOImageLoader.decodeJPEGBaseline(dataSet, frame);
}
// JPEG Baseline lossy process 2 & 4 (12 bit)
else if ( transferSyntax === "1.2.840.10008.1.2.4.51" )
{
return cornerstoneWADOImageLoader.decodeJPEGBaseline(dataSet, frame);
}
// JPEG Lossless, Nonhierarchical (Processes 14)
else if ( transferSyntax === "1.2.840.10008.1.2.4.57" )
{
return cornerstoneWADOImageLoader.decodeJPEGLossless(dataSet, frame);
}
// JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
else if ( transferSyntax === "1.2.840.10008.1.2.4.70" )
{
return cornerstoneWADOImageLoader.decodeJPEGLossless(dataSet, frame);
}
else
{
throw "no decoder for transfer syntax " + transferSyntax;
}
}
// module exports
cornerstoneWADOImageLoader.decodeTransferSyntax = decodeTransferSyntax;
}(cornerstoneWADOImageLoader));
// jshint ignore: start
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
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.
*/
// - The JPEG specification can be found in the ITU CCITT Recommendation T.81
// (www.w3.org/Graphics/JPEG/itu-t81.pdf)
// - The JFIF specification can be found in the JPEG File Interchange Format
// (www.w3.org/Graphics/JPEG/jfif3.pdf)
// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
// in PostScript Level 2, Technical Note #5116
// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
var ColorSpace = {Unkown: 0, Grayscale: 1, AdobeRGB: 2, RGB: 3, CYMK: 4};
var JpegImage = (function jpegImage() {
"use strict";
var dctZigZag = new Int32Array([
0,
1, 8,
16, 9, 2,
3, 10, 17, 24,
32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40,
48, 41, 34, 27, 20, 13, 6,
7, 14, 21, 28, 35, 42, 49, 56,
57, 50, 43, 36, 29, 22, 15,
23, 30, 37, 44, 51, 58,
59, 52, 45, 38, 31,
39, 46, 53, 60,
61, 54, 47,
55, 62,
63
]);
var dctCos1 = 4017; // cos(pi/16)
var dctSin1 = 799; // sin(pi/16)
var dctCos3 = 3406; // cos(3*pi/16)
var dctSin3 = 2276; // sin(3*pi/16)
var dctCos6 = 1567; // cos(6*pi/16)
var dctSin6 = 3784; // sin(6*pi/16)
var dctSqrt2 = 5793; // sqrt(2)
var dctSqrt1d2 = 2896; // sqrt(2) / 2
function constructor() {
}
function buildHuffmanTable(codeLengths, values) {
var k = 0, code = [], i, j, length = 16;
while (length > 0 && !codeLengths[length - 1])
length--;
code.push({children: [], index: 0});
var p = code[0], q;
for (i = 0; i < length; i++) {
for (j = 0; j < codeLengths[i]; j++) {
p = code.pop();
p.children[p.index] = values[k];
while (p.index > 0) {
p = code.pop();
}
p.index++;
code.push(p);
while (code.length <= i) {
code.push(q = {children: [], index: 0});
p.children[p.index] = q.children;
p = q;
}
k++;
}
if (i + 1 < length) {
// p here points to last code
code.push(q = {children: [], index: 0});
p.children[p.index] = q.children;
p = q;
}
}
return code[0].children;
}
function getBlockBufferOffset(component, row, col) {
return 64 * ((component.blocksPerLine + 1) * row + col);
}
function decodeScan(data, offset,
frame, components, resetInterval,
spectralStart, spectralEnd,
successivePrev, successive) {
var precision = frame.precision;
var samplesPerLine = frame.samplesPerLine;
var scanLines = frame.scanLines;
var mcusPerLine = frame.mcusPerLine;
var progressive = frame.progressive;
var maxH = frame.maxH, maxV = frame.maxV;
var startOffset = offset, bitsData = 0, bitsCount = 0;
function readBit() {
if (bitsCount > 0) {
bitsCount--;
return (bitsData >> bitsCount) & 1;
}
bitsData = data[offset++];
if (bitsData == 0xFF) {
var nextByte = data[offset++];
if (nextByte) {
throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16);
}
// unstuff 0
}
bitsCount = 7;
return bitsData >>> 7;
}
function decodeHuffman(tree) {
var node = tree;
var bit;
while ((bit = readBit()) !== null) {
node = node[bit];
if (typeof node === 'number')
return node;
if (typeof node !== 'object')
throw "invalid huffman sequence";
}
return null;
}
function receive(length) {
var n = 0;
while (length > 0) {
var bit = readBit();
if (bit === null)
return;
n = (n << 1) | bit;
length--;
}
return n;
}
function receiveAndExtend(length) {
var n = receive(length);
if (n >= 1 << (length - 1))
return n;
return n + (-1 << length) + 1;
}
function decodeBaseline(component, offset) {
var t = decodeHuffman(component.huffmanTableDC);
var diff = t === 0 ? 0 : receiveAndExtend(t);
component.blockData[offset] = (component.pred += diff);
var k = 1;
while (k < 64) {
var rs = decodeHuffman(component.huffmanTableAC);
var s = rs & 15, r = rs >> 4;
if (s === 0) {
if (r < 15)
break;
k += 16;
continue;
}
k += r;
var z = dctZigZag[k];
component.blockData[offset + z] = receiveAndExtend(s);
k++;
}
}
function decodeDCFirst(component, offset) {
var t = decodeHuffman(component.huffmanTableDC);
var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
component.blockData[offset] = (component.pred += diff);
}
function decodeDCSuccessive(component, offset) {
component.blockData[offset] |= readBit() << successive;
}
var eobrun = 0;
function decodeACFirst(component, offset) {
if (eobrun > 0) {
eobrun--;
return;
}
var k = spectralStart, e = spectralEnd;
while (k <= e) {
var rs = decodeHuffman(component.huffmanTableAC);
var s = rs & 15, r = rs >> 4;
if (s === 0) {
if (r < 15) {
eobrun = receive(r) + (1 << r) - 1;
break;
}
k += 16;
continue;
}
k += r;
var z = dctZigZag[k];
component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive);
k++;
}
}
var successiveACState = 0, successiveACNextValue;
function decodeACSuccessive(component, offset) {
var k = spectralStart, e = spectralEnd, r = 0;
while (k <= e) {
var z = dctZigZag[k];
switch (successiveACState) {
case 0: // initial state
var rs = decodeHuffman(component.huffmanTableAC);
var s = rs & 15;
r = rs >> 4;
if (s === 0) {
if (r < 15) {
eobrun = receive(r) + (1 << r);
successiveACState = 4;
} else {
r = 16;
successiveACState = 1;
}
} else {
if (s !== 1)
throw "invalid ACn encoding";
successiveACNextValue = receiveAndExtend(s);
successiveACState = r ? 2 : 3;
}
continue;
case 1: // skipping r zero items
case 2:
if (component.blockData[offset + z]) {
component.blockData[offset + z] += (readBit() << successive);
} else {
r--;
if (r === 0)
successiveACState = successiveACState == 2 ? 3 : 0;
}
break;
case 3: // set value for a zero item
if (component.blockData[offset + z]) {
component.blockData[offset + z] += (readBit() << successive);
} else {
component.blockData[offset + z] = successiveACNextValue << successive;
successiveACState = 0;
}
break;
case 4: // eob
if (component.blockData[offset + z]) {
component.blockData[offset + z] += (readBit() << successive);
}
break;
}
k++;
}
if (successiveACState === 4) {
eobrun--;
if (eobrun === 0)
successiveACState = 0;
}
}
function decodeMcu(component, decode, mcu, row, col) {
var mcuRow = (mcu / mcusPerLine) | 0;
var mcuCol = mcu % mcusPerLine;
var blockRow = mcuRow * component.v + row;
var blockCol = mcuCol * component.h + col;
var offset = getBlockBufferOffset(component, blockRow, blockCol);
decode(component, offset);
}
function decodeBlock(component, decode, mcu) {
var blockRow = (mcu / component.blocksPerLine) | 0;
var blockCol = mcu % component.blocksPerLine;
var offset = getBlockBufferOffset(component, blockRow, blockCol);
decode(component, offset);
}
var componentsLength = components.length;
var component, i, j, k, n;
var decodeFn;
if (progressive) {
if (spectralStart === 0)
decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
else
decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
} else {
decodeFn = decodeBaseline;
}
var mcu = 0, marker;
var mcuExpected;
if (componentsLength == 1) {
mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
} else {
mcuExpected = mcusPerLine * frame.mcusPerColumn;
}
if (!resetInterval) {
resetInterval = mcuExpected;
}
var h, v;
while (mcu < mcuExpected) {
// reset interval stuff
for (i = 0; i < componentsLength; i++) {
components[i].pred = 0;
}
eobrun = 0;
if (componentsLength == 1) {
component = components[0];
for (n = 0; n < resetInterval; n++) {
decodeBlock(component, decodeFn, mcu);
mcu++;
}
} else {
for (n = 0; n < resetInterval; n++) {
for (i = 0; i < componentsLength; i++) {
component = components[i];
h = component.h;
v = component.v;
for (j = 0; j < v; j++) {
for (k = 0; k < h; k++) {
decodeMcu(component, decodeFn, mcu, j, k);
}
}
}
mcu++;
}
}
// find marker
bitsCount = 0;
marker = (data[offset] << 8) | data[offset + 1];
if (marker <= 0xFF00) {
throw "marker was not found";
}
if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
offset += 2;
} else {
break;
}
}
return offset - startOffset;
}
// A port of poppler's IDCT method which in turn is taken from:
// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
// "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
// 988-991.
function quantizeAndInverse(component, blockBufferOffset, p) {
var qt = component.quantizationTable;
var v0, v1, v2, v3, v4, v5, v6, v7, t;
var i;
// dequant
for (i = 0; i < 64; i++) {
p[i] = component.blockData[blockBufferOffset + i] * qt[i];
}
// inverse DCT on rows
for (i = 0; i < 8; ++i) {
var row = 8 * i;
// check for all-zero AC coefficients
if (p[1 + row] === 0 && p[2 + row] === 0 && p[3 + row] === 0 &&
p[4 + row] === 0 && p[5 + row] === 0 && p[6 + row] === 0 &&
p[7 + row] === 0) {
t = (dctSqrt2 * p[0 + row] + 512) >> 10;
p[0 + row] = t;
p[1 + row] = t;
p[2 + row] = t;
p[3 + row] = t;
p[4 + row] = t;
p[5 + row] = t;
p[6 + row] = t;
p[7 + row] = t;
continue;
}
// stage 4
v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
v2 = p[2 + row];
v3 = p[6 + row];
v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
v5 = p[3 + row] << 4;
v6 = p[5 + row] << 4;
// stage 3
t = (v0 - v1 + 1) >> 1;
v0 = (v0 + v1 + 1) >> 1;
v1 = t;
t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
v3 = t;
t = (v4 - v6 + 1) >> 1;
v4 = (v4 + v6 + 1) >> 1;
v6 = t;
t = (v7 + v5 + 1) >> 1;
v5 = (v7 - v5 + 1) >> 1;
v7 = t;
// stage 2
t = (v0 - v3 + 1) >> 1;
v0 = (v0 + v3 + 1) >> 1;
v3 = t;
t = (v1 - v2 + 1) >> 1;
v1 = (v1 + v2 + 1) >> 1;
v2 = t;
t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
v7 = t;
t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
v6 = t;
// stage 1
p[0 + row] = v0 + v7;
p[7 + row] = v0 - v7;
p[1 + row] = v1 + v6;
p[6 + row] = v1 - v6;
p[2 + row] = v2 + v5;
p[5 + row] = v2 - v5;
p[3 + row] = v3 + v4;
p[4 + row] = v3 - v4;
}
// inverse DCT on columns
for (i = 0; i < 8; ++i) {
var col = i;
// check for all-zero AC coefficients
if (p[1 * 8 + col] === 0 && p[2 * 8 + col] === 0 && p[3 * 8 + col] === 0 &&
p[4 * 8 + col] === 0 && p[5 * 8 + col] === 0 && p[6 * 8 + col] === 0 &&
p[7 * 8 + col] === 0) {
t = (dctSqrt2 * p[i + 0] + 8192) >> 14;
p[0 * 8 + col] = t;
p[1 * 8 + col] = t;
p[2 * 8 + col] = t;
p[3 * 8 + col] = t;
p[4 * 8 + col] = t;
p[5 * 8 + col] = t;
p[6 * 8 + col] = t;
p[7 * 8 + col] = t;
continue;
}
// stage 4
v0 = (dctSqrt2 * p[0 * 8 + col] + 2048) >> 12;
v1 = (dctSqrt2 * p[4 * 8 + col] + 2048) >> 12;
v2 = p[2 * 8 + col];
v3 = p[6 * 8 + col];
v4 = (dctSqrt1d2 * (p[1 * 8 + col] - p[7 * 8 + col]) + 2048) >> 12;
v7 = (dctSqrt1d2 * (p[1 * 8 + col] + p[7 * 8 + col]) + 2048) >> 12;
v5 = p[3 * 8 + col];
v6 = p[5 * 8 + col];
// stage 3
t = (v0 - v1 + 1) >> 1;
v0 = (v0 + v1 + 1) >> 1;
v1 = t;
t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
v3 = t;
t = (v4 - v6 + 1) >> 1;
v4 = (v4 + v6 + 1) >> 1;
v6 = t;
t = (v7 + v5 + 1) >> 1;
v5 = (v7 - v5 + 1) >> 1;
v7 = t;
// stage 2
t = (v0 - v3 + 1) >> 1;
v0 = (v0 + v3 + 1) >> 1;
v3 = t;
t = (v1 - v2 + 1) >> 1;
v1 = (v1 + v2 + 1) >> 1;
v2 = t;
t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
v7 = t;
t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
v6 = t;
// stage 1
p[0 * 8 + col] = v0 + v7;
p[7 * 8 + col] = v0 - v7;
p[1 * 8 + col] = v1 + v6;
p[6 * 8 + col] = v1 - v6;
p[2 * 8 + col] = v2 + v5;
p[5 * 8 + col] = v2 - v5;
p[3 * 8 + col] = v3 + v4;
p[4 * 8 + col] = v3 - v4;
}
// convert to 8-bit integers
for (i = 0; i < 64; ++i) {
var index = blockBufferOffset + i;
var q = p[i];
q = (q <= -2056 / component.bitConversion) ? 0 :
(q >= 2024 / component.bitConversion) ? 255 / component.bitConversion :
(q + 2056 / component.bitConversion) >> 4;
component.blockData[index] = q;
}
}
function buildComponentData(frame, component) {
var lines = [];
var blocksPerLine = component.blocksPerLine;
var blocksPerColumn = component.blocksPerColumn;
var samplesPerLine = blocksPerLine << 3;
var computationBuffer = new Int32Array(64);
var i, j, ll = 0;
for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
var offset = getBlockBufferOffset(component, blockRow, blockCol);
quantizeAndInverse(component, offset, computationBuffer);
}
}
return component.blockData;
}
function clampToUint8(a) {
return a <= 0 ? 0 : a >= 255 ? 255 : a | 0;
}
constructor.prototype = {
load: function load(path) {
var handleData = (function (data) {
this.parse(data);
if (this.onload)
this.onload();
}).bind(this);
if (path.indexOf("data:") > -1) {
var offset = path.indexOf("base64,") + 7;
var data = atob(path.substring(offset));
var arr = new Uint8Array(data.length);
for (var i = data.length - 1; i >= 0; i--) {
arr[i] = data.charCodeAt(i);
}
handleData(data);
} else {
var xhr = new XMLHttpRequest();
xhr.open("GET", path, true);
xhr.responseType = "arraybuffer";
xhr.onload = (function () {
// TODO catch parse error
var data = new Uint8Array(xhr.response);
handleData(data);
}).bind(this);
xhr.send(null);
}
},
parse: function parse(data) {
function readUint16() {
var value = (data[offset] << 8) | data[offset + 1];
offset += 2;
return value;
}
function readDataBlock() {
var length = readUint16();
var array = data.subarray(offset, offset + length - 2);
offset += array.length;
return array;
}
function prepareComponents(frame) {
var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
for (var i = 0; i < frame.components.length; i++) {
component = frame.components[i];
var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV);
var blocksPerLineForMcu = mcusPerLine * component.h;
var blocksPerColumnForMcu = mcusPerColumn * component.v;
var blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
component.blockData = new Int16Array(blocksBufferSize);
component.blocksPerLine = blocksPerLine;
component.blocksPerColumn = blocksPerColumn;
}
frame.mcusPerLine = mcusPerLine;
frame.mcusPerColumn = mcusPerColumn;
}
var offset = 0, length = data.length;
var jfif = null;
var adobe = null;
var pixels = null;
var frame, resetInterval;
var quantizationTables = [];
var huffmanTablesAC = [], huffmanTablesDC = [];
var fileMarker = readUint16();
if (fileMarker != 0xFFD8) { // SOI (Start of Image)
throw "SOI not found";
}
fileMarker = readUint16();
while (fileMarker != 0xFFD9) { // EOI (End of image)
var i, j, l;
switch (fileMarker) {
case 0xFFE0: // APP0 (Application Specific)
case 0xFFE1: // APP1
case 0xFFE2: // APP2
case 0xFFE3: // APP3
case 0xFFE4: // APP4
case 0xFFE5: // APP5
case 0xFFE6: // APP6
case 0xFFE7: // APP7
case 0xFFE8: // APP8
case 0xFFE9: // APP9
case 0xFFEA: // APP10
case 0xFFEB: // APP11
case 0xFFEC: // APP12
case 0xFFED: // APP13
case 0xFFEE: // APP14
case 0xFFEF: // APP15
case 0xFFFE: // COM (Comment)
var appData = readDataBlock();
if (fileMarker === 0xFFE0) {
if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 &&
appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00'
jfif = {
version: {major: appData[5], minor: appData[6]},
densityUnits: appData[7],
xDensity: (appData[8] << 8) | appData[9],
yDensity: (appData[10] << 8) | appData[11],
thumbWidth: appData[12],
thumbHeight: appData[13],
thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
};
}
}
// TODO APP1 - Exif
if (fileMarker === 0xFFEE) {
if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F &&
appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
adobe = {
version: appData[6],
flags0: (appData[7] << 8) | appData[8],
flags1: (appData[9] << 8) | appData[10],
transformCode: appData[11]
};
}
}
break;
case 0xFFDB: // DQT (Define Quantization Tables)
var quantizationTablesLength = readUint16();
var quantizationTablesEnd = quantizationTablesLength + offset - 2;
while (offset < quantizationTablesEnd) {
var quantizationTableSpec = data[offset++];
var tableData = new Int32Array(64);
if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
for (j = 0; j < 64; j++) {
var z = dctZigZag[j];
tableData[z] = data[offset++];
}
} else if ((quantizationTableSpec >> 4) === 1) { //16 bit
for (j = 0; j < 64; j++) {
var zz = dctZigZag[j];
tableData[zz] = readUint16();
}
} else
throw "DQT: invalid table spec";
quantizationTables[quantizationTableSpec & 15] = tableData;
}
break;
case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
if (frame) {
throw "Only single frame JPEGs supported";
}
readUint16(); // skip data length
frame = {};
frame.extended = (fileMarker === 0xFFC1);
frame.progressive = (fileMarker === 0xFFC2);
frame.precision = data[offset++];
frame.scanLines = readUint16();
frame.samplesPerLine = readUint16();
frame.components = [];
frame.componentIds = {};
var componentsCount = data[offset++], componentId;
var maxH = 0, maxV = 0;
for (i = 0; i < componentsCount; i++) {
componentId = data[offset];
var h = data[offset + 1] >> 4;
var v = data[offset + 1] & 15;
if (maxH < h)
maxH = h;
if (maxV < v)
maxV = v;
var qId = data[offset + 2];
l = frame.components.push({
h: h,
v: v,
quantizationTable: quantizationTables[qId],
quantizationTableId: qId,
bitConversion: 255 / ((1 << frame.precision) - 1)
});
frame.componentIds[componentId] = l - 1;
offset += 3;
}
frame.maxH = maxH;
frame.maxV = maxV;
prepareComponents(frame);
break;
case 0xFFC4: // DHT (Define Huffman Tables)
var huffmanLength = readUint16();
for (i = 2; i < huffmanLength; ) {
var huffmanTableSpec = data[offset++];
var codeLengths = new Uint8Array(16);
var codeLengthSum = 0;
for (j = 0; j < 16; j++, offset++)
codeLengthSum += (codeLengths[j] = data[offset]);
var huffmanValues = new Uint8Array(codeLengthSum);
for (j = 0; j < codeLengthSum; j++, offset++)
huffmanValues[j] = data[offset];
i += 17 + codeLengthSum;
((huffmanTableSpec >> 4) === 0 ?
huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] =
buildHuffmanTable(codeLengths, huffmanValues);
}
break;
case 0xFFDD: // DRI (Define Restart Interval)
readUint16(); // skip data length
resetInterval = readUint16();
break;
case 0xFFDA: // SOS (Start of Scan)
var scanLength = readUint16();
var selectorsCount = data[offset++];
var components = [], component;
for (i = 0; i < selectorsCount; i++) {
var componentIndex = frame.componentIds[data[offset++]];
component = frame.components[componentIndex];
var tableSpec = data[offset++];
component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
components.push(component);
}
var spectralStart = data[offset++];
var spectralEnd = data[offset++];
var successiveApproximation = data[offset++];
var processed = decodeScan(data, offset,
frame, components, resetInterval,
spectralStart, spectralEnd,
successiveApproximation >> 4, successiveApproximation & 15);
offset += processed;
break;
default:
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;
break;
}
throw "unknown JPEG marker " + fileMarker.toString(16);
}
fileMarker = readUint16();
}
this.width = frame.samplesPerLine;
this.height = frame.scanLines;
this.jfif = jfif;
this.adobe = adobe;
this.components = [];
switch (frame.components.length)
{
case 1:
this.colorspace = ColorSpace.Grayscale;
break;
case 3:
if (this.adobe)
this.colorspace = ColorSpace.AdobeRGB;
else
this.colorspace = ColorSpace.RGB;
break;
case 4:
this.colorspace = ColorSpace.CYMK;
break;
default:
this.colorspace = ColorSpace.Unknown;
}
for (var i = 0; i < frame.components.length; i++) {
var component = frame.components[i];
if (!component.quantizationTable && component.quantizationTableId !== null)
component.quantizationTable = quantizationTables[component.quantizationTableId];
this.components.push({
output: buildComponentData(frame, component),
scaleX: component.h / frame.maxH,
scaleY: component.v / frame.maxV,
blocksPerLine: component.blocksPerLine,
blocksPerColumn: component.blocksPerColumn,
bitConversion: component.bitConversion
});
}
},
getData16: function getData16(width, height) {
if (this.components.length !== 1)
throw 'Unsupported color mode';
var scaleX = this.width / width, scaleY = this.height / height;
var component, componentScaleX, componentScaleY;
var x, y, i;
var offset = 0;
var numComponents = this.components.length;
var dataLength = width * height * numComponents;
var data = new Uint16Array(dataLength);
var componentLine;
// lineData is reused for all components. Assume first component is
// the biggest
var lineData = new Uint16Array((this.components[0].blocksPerLine << 3) *
this.components[0].blocksPerColumn * 8);
// First construct image data ...
for (i = 0; i < numComponents; i++) {
component = this.components[i];
var blocksPerLine = component.blocksPerLine;
var blocksPerColumn = component.blocksPerColumn;
var samplesPerLine = blocksPerLine << 3;
var j, k, ll = 0;
var lineOffset = 0;
for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
var scanLine = blockRow << 3;
for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol);
var offset = 0, sample = blockCol << 3;
for (j = 0; j < 8; j++) {
var lineOffset = (scanLine + j) * samplesPerLine;
for (k = 0; k < 8; k++) {
lineData[lineOffset + sample + k] =
component.output[bufferOffset + offset++];
}
}
}
}
componentScaleX = component.scaleX * scaleX;
componentScaleY = component.scaleY * scaleY;
offset = i;
var cx, cy;
var index;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
cy = 0 | (y * componentScaleY);
cx = 0 | (x * componentScaleX);
index = cy * samplesPerLine + cx;
data[offset] = lineData[index];
offset += numComponents;
}
}
}
return data;
},
getData: function getData(width, height) {
var scaleX = this.width / width, scaleY = this.height / height;
var component, componentScaleX, componentScaleY;
var x, y, i;
var offset = 0;
var Y, Cb, Cr, K, C, M, Ye, R, G, B;
var colorTransform;
var numComponents = this.components.length;
var dataLength = width * height * numComponents;
var data = new Uint8Array(dataLength);
var componentLine;
// lineData is reused for all components. Assume first component is
// the biggest
var lineData = new Uint8Array((this.components[0].blocksPerLine << 3) *
this.components[0].blocksPerColumn * 8);
// First construct image data ...
for (i = 0; i < numComponents; i++) {
component = this.components[i];
var blocksPerLine = component.blocksPerLine;
var blocksPerColumn = component.blocksPerColumn;
var samplesPerLine = blocksPerLine << 3;
var j, k, ll = 0;
var lineOffset = 0;
for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
var scanLine = blockRow << 3;
for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol);
var offset = 0, sample = blockCol << 3;
for (j = 0; j < 8; j++) {
var lineOffset = (scanLine + j) * samplesPerLine;
for (k = 0; k < 8; k++) {
lineData[lineOffset + sample + k] =
component.output[bufferOffset + offset++] * component.bitConversion;
}
}
}
}
componentScaleX = component.scaleX * scaleX;
componentScaleY = component.scaleY * scaleY;
offset = i;
var cx, cy;
var index;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
cy = 0 | (y * componentScaleY);
cx = 0 | (x * componentScaleX);
index = cy * samplesPerLine + cx;
data[offset] = lineData[index];
offset += numComponents;
}
}
}
// ... then transform colors, if necessary
switch (numComponents) {
case 1:
case 2:
break;
// no color conversion for one or two compoenents
case 3:
// The default transform for three components is true
colorTransform = true;
// The adobe transform marker overrides any previous setting
if (this.adobe && this.adobe.transformCode)
colorTransform = true;
else if (typeof this.colorTransform !== 'undefined')
colorTransform = !!this.colorTransform;
if (colorTransform) {
for (i = 0; i < dataLength; i += numComponents) {
Y = data[i ];
Cb = data[i + 1];
Cr = data[i + 2];
R = clampToUint8(Y - 179.456 + 1.402 * Cr);
G = clampToUint8(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
B = clampToUint8(Y - 226.816 + 1.772 * Cb);
data[i ] = R;
data[i + 1] = G;
data[i + 2] = B;
}
}
break;
case 4:
if (!this.adobe)
throw 'Unsupported color mode (4 components)';
// The default transform for four components is false
colorTransform = false;
// The adobe transform marker overrides any previous setting
if (this.adobe && this.adobe.transformCode)
colorTransform = true;
else if (typeof this.colorTransform !== 'undefined')
colorTransform = !!this.colorTransform;
if (colorTransform) {
for (i = 0; i < dataLength; i += numComponents) {
Y = data[i];
Cb = data[i + 1];
Cr = data[i + 2];
C = clampToUint8(434.456 - Y - 1.402 * Cr);
M = clampToUint8(119.541 - Y + 0.344 * Cb + 0.714 * Cr);
Y = clampToUint8(481.816 - Y - 1.772 * Cb);
data[i ] = C;
data[i + 1] = M;
data[i + 2] = Y;
// K is unchanged
}
}
break;
default:
throw 'Unsupported color mode';
}
return data;
}
};
return constructor;
})();
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jpeg = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
/*** Constructor ***/
jpeg.lossless.ComponentSpec = jpeg.lossless.ComponentSpec || function () {
this.hSamp = 0; // Horizontal sampling factor
this.quantTableSel = 0; // Quantization table destination selector
this.vSamp = 0; // Vertical
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.ComponentSpec;
}
},{}],2:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
/*** Constructor ***/
jpeg.lossless.DataStream = jpeg.lossless.DataStream || function (data, offset, length) {
this.buffer = new DataView(data, offset, length);
this.index = 0;
};
jpeg.lossless.DataStream.prototype.get16 = function () {
var value = this.buffer.getUint16(this.index, false);
this.index += 2;
return value;
};
jpeg.lossless.DataStream.prototype.get8 = function () {
var value = this.buffer.getUint8(this.index);
this.index += 1;
return value;
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.DataStream;
}
},{}],3:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
jpeg.lossless.DataStream = jpeg.lossless.DataStream || ((typeof require !== 'undefined') ? require('./data-stream.js') : null);
jpeg.lossless.HuffmanTable = jpeg.lossless.HuffmanTable || ((typeof require !== 'undefined') ? require('./huffman-table.js') : null);
jpeg.lossless.QuantizationTable = jpeg.lossless.QuantizationTable || ((typeof require !== 'undefined') ? require('./quantization-table.js') : null);
jpeg.lossless.ScanHeader = jpeg.lossless.ScanHeader || ((typeof require !== 'undefined') ? require('./scan-header.js') : null);
jpeg.lossless.FrameHeader = jpeg.lossless.FrameHeader || ((typeof require !== 'undefined') ? require('./frame-header.js') : null);
jpeg.lossless.Utils = jpeg.lossless.Utils || ((typeof require !== 'undefined') ? require('./utils.js') : null);
/*** Constructor ***/
jpeg.lossless.Decoder = jpeg.lossless.Decoder || function (buffer, numBytes) {
this.buffer = buffer;
this.frame = new jpeg.lossless.FrameHeader();
this.huffTable = new jpeg.lossless.HuffmanTable();
this.quantTable = new jpeg.lossless.QuantizationTable();
this.scan = new jpeg.lossless.ScanHeader();
this.DU = jpeg.lossless.Utils.createArray(10, 4, 64); // at most 10 data units in a MCU, at most 4 data units in one component
this.HuffTab = jpeg.lossless.Utils.createArray(4, 2, 50 * 256);
this.IDCT_Source = [];
this.nBlock = []; // number of blocks in the i-th Comp in a scan
this.acTab = jpeg.lossless.Utils.createArray(10, 1); // ac HuffTab for the i-th Comp in a scan
this.dcTab = jpeg.lossless.Utils.createArray(10, 1); // dc HuffTab for the i-th Comp in a scan
this.qTab = jpeg.lossless.Utils.createArray(10, 1); // quantization table for the i-th Comp in a scan
this.marker = 0;
this.markerIndex = 0;
this.numComp = 0;
this.restartInterval = 0;
this.selection = 0;
this.xDim = 0;
this.yDim = 0;
this.xLoc = 0;
this.yLoc = 0;
this.numBytes = 0;
this.outputData = null;
if (typeof numBytes !== "undefined") {
this.numBytes = numBytes;
}
};
/*** Static Pseudo-constants ***/
jpeg.lossless.Decoder.IDCT_P = [0, 5, 40, 16, 45, 2, 7, 42, 21, 56, 8, 61, 18, 47, 1, 4, 41, 23, 58, 13, 32, 24, 37, 10, 63, 17, 44, 3, 6, 43, 20,
57, 15, 34, 29, 48, 53, 26, 39, 9, 60, 19, 46, 22, 59, 12, 33, 31, 50, 55, 25, 36, 11, 62, 14, 35, 28, 49, 52, 27, 38, 30, 51, 54];
jpeg.lossless.Decoder.TABLE = [0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63];
jpeg.lossless.Decoder.MAX_HUFFMAN_SUBTREE = 50;
jpeg.lossless.Decoder.MSB = 0x80000000;
/*** Prototype Methods ***/
jpeg.lossless.Decoder.prototype.decompress = function (buffer, offset, length) {
return this.decode(buffer, offset, length).buffer;
};
jpeg.lossless.Decoder.prototype.decode = function (buffer, offset, length, numBytes) {
/*jslint bitwise: true */
var current, scanNum = 0, pred = [], i, compN, temp = [], index = [], mcuNum;
if (typeof buffer !== "undefined") {
this.buffer = buffer;
}
if (typeof numBytes !== "undefined") {
this.numBytes = numBytes;
}
this.stream = new jpeg.lossless.DataStream(this.buffer, offset, length);
this.buffer = null;
this.xLoc = 0;
this.yLoc = 0;
current = this.stream.get16();
if (current !== 0xFFD8) { // SOI
throw new Error("Not a JPEG file");
}
current = this.stream.get16();
while ((((current >> 4) !== 0x0FFC) || (current === 0xFFC4))) { // SOF 0~15
switch (current) {
case 0xFFC4: // DHT
this.huffTable.read(this.stream, this.HuffTab);
break;
case 0xFFCC: // DAC
throw new Error("Program doesn't support arithmetic coding. (format throw new IOException)");
case 0xFFDB:
this.quantTable.read(this.stream, jpeg.lossless.Decoder.TABLE);
break;
case 0xFFDD:
this.restartInterval = this.readNumber();
break;
case 0xFFE0:
case 0xFFE1:
case 0xFFE2:
case 0xFFE3:
case 0xFFE4:
case 0xFFE5:
case 0xFFE6:
case 0xFFE7:
case 0xFFE8:
case 0xFFE9:
case 0xFFEA:
case 0xFFEB:
case 0xFFEC:
case 0xFFED:
case 0xFFEE:
case 0xFFEF:
this.readApp();
break;
case 0xFFFE:
this.readComment();
break;
default:
if ((current >> 8) !== 0xFF) {
throw new Error("ERROR: format throw new IOException! (decode)");
}
}
current = this.stream.get16();
}
if ((current < 0xFFC0) || (current > 0xFFC7)) {
throw new Error("ERROR: could not handle arithmetic code!");
}
this.frame.read(this.stream);
current = this.stream.get16();
do {
while (current !== 0x0FFDA) { // SOS
switch (current) {
case 0xFFC4: // DHT
this.huffTable.read(this.stream, this.HuffTab);
break;
case 0xFFCC: // DAC
throw new Error("Program doesn't support arithmetic coding. (format throw new IOException)");
case 0xFFDB:
this.quantTable.read(this.stream, jpeg.lossless.Decoder.TABLE);
break;
case 0xFFDD:
this.restartInterval = this.readNumber();
break;
case 0xFFE0:
case 0xFFE1:
case 0xFFE2:
case 0xFFE3:
case 0xFFE4:
case 0xFFE5:
case 0xFFE6:
case 0xFFE7:
case 0xFFE8:
case 0xFFE9:
case 0xFFEA:
case 0xFFEB:
case 0xFFEC:
case 0xFFED:
case 0xFFEE:
case 0xFFEF:
this.readApp();
break;
case 0xFFFE:
this.readComment();
break;
default:
if ((current >> 8) !== 0xFF) {
throw new Error("ERROR: format throw new IOException! (Parser.decode)");
}
}
current = this.stream.get16();
}
this.precision = this.frame.precision;
this.components = this.frame.components;
if (!this.numBytes) {
this.numBytes = parseInt(this.precision / 8);
}
this.scan.read(this.stream);
this.numComp = this.scan.numComp;
this.selection = this.scan.selection;
if (this.numBytes === 1) {
if (this.numComp === 3) {
this.getter = this.getValueRGB;
this.setter = this.setValueRGB;
this.output = this.outputRGB;
} else {
this.getter = this.getValue8;
this.setter = this.setValue8;
this.output = this.outputSingle;
}
} else {
this.getter = this.getValue16;
this.setter = this.setValue16;
this.output = this.outputSingle;
}
switch (this.selection) {
case 2:
this.selector = this.select2;
break;
case 3:
this.selector = this.select3;
break;
case 4:
this.selector = this.select4;
break;
case 5:
this.selector = this.select5;
break;
case 6:
this.selector = this.select6;
break;
case 7:
this.selector = this.select7;
break;
default:
this.selector = this.select1;
break;
}
this.scanComps = this.scan.components;
this.quantTables = this.quantTable.quantTables;
for (i = 0; i < this.numComp; i+=1) {
compN = this.scanComps[i].scanCompSel;
this.qTab[i] = this.quantTables[this.components[compN].quantTableSel];
this.nBlock[i] = this.components[compN].vSamp * this.components[compN].hSamp;
this.dcTab[i] = this.HuffTab[this.scanComps[i].dcTabSel][0];
this.acTab[i] = this.HuffTab[this.scanComps[i].acTabSel][1];
}
this.xDim = this.frame.dimX;
this.yDim = this.frame.dimY;
this.outputData = new DataView(new ArrayBuffer(this.xDim * this.yDim * this.numBytes * this.numComp));
scanNum+=1;
while (true) { // Decode one scan
temp[0] = 0;
index[0] = 0;
for (i = 0; i < 10; i+=1) {
pred[i] = (1 << (this.precision - 1));
}
if (this.restartInterval === 0) {
current = this.decodeUnit(pred, temp, index);
while ((current === 0) && ((this.xLoc < this.xDim) && (this.yLoc < this.yDim))) {
this.output(pred);
current = this.decodeUnit(pred, temp, index);
}
break; //current=MARKER
}
for (mcuNum = 0; mcuNum < this.restartInterval; mcuNum+=1) {
current = this.decodeUnit(pred, temp, index);
this.output(pred);
if (current !== 0) {
break;
}
}
if (current === 0) {
if (this.markerIndex !== 0) {
current = (0xFF00 | this.marker);
this.markerIndex = 0;
} else {
current = this.stream.get16();
}
}
if (!((current >= 0xFFD0) && (current <= 0xFFD7))) {
break; //current=MARKER
}
}
if ((current === 0xFFDC) && (scanNum === 1)) { //DNL
this.readNumber();
current = this.stream.get16();
}
} while ((current !== 0xFFD9) && ((this.xLoc < this.xDim) && (this.yLoc < this.yDim)) && (scanNum === 0));
return this.outputData;
};
jpeg.lossless.Decoder.prototype.decodeUnit = function (prev, temp, index) {
if (this.numComp == 1) {
return this.decodeSingle(prev, temp, index);
} else if (this.numComp == 3) {
return this.decodeRGB(prev, temp, index);
} else {
return -1;
}
};
jpeg.lossless.Decoder.prototype.select1 = function (compOffset) {
return this.getPreviousX(compOffset);
};
jpeg.lossless.Decoder.prototype.select2 = function (compOffset) {
return this.getPreviousY(compOffset);
};
jpeg.lossless.Decoder.prototype.select3 = function (compOffset) {
return this.getPreviousXY(compOffset);
};
jpeg.lossless.Decoder.prototype.select4 = function (compOffset) {
return (this.getPreviousX(compOffset) + this.getPreviousY(compOffset)) - this.getPreviousXY(compOffset);
};
jpeg.lossless.Decoder.prototype.select5 = function (compOffset) {
return this.getPreviousX(compOffset) + ((this.getPreviousY(compOffset) - this.getPreviousXY(compOffset)) >> 1);
};
jpeg.lossless.Decoder.prototype.select6 = function (compOffset) {
return this.getPreviousY(compOffset) + ((this.getPreviousX(compOffset) - this.getPreviousXY(compOffset)) >> 1);
};
jpeg.lossless.Decoder.prototype.select7 = function (compOffset) {
return ((this.getPreviousX(compOffset) + this.getPreviousY(compOffset)) / 2);
};
jpeg.lossless.Decoder.prototype.decodeRGB = function (prev, temp, index) {
/*jslint bitwise: true */
var value, actab, dctab, qtab, ctrC, i, k, j;
prev[0] = this.selector(0);
prev[1] = this.selector(1);
prev[2] = this.selector(2);
for (ctrC = 0; ctrC < this.numComp; ctrC+=1) {
qtab = this.qTab[ctrC];
actab = this.acTab[ctrC];
dctab = this.dcTab[ctrC];
for (i = 0; i < this.nBlock[ctrC]; i+=1) {
for (k = 0; k < this.IDCT_Source.length; k+=1) {
this.IDCT_Source[k] = 0;
}
value = this.getHuffmanValue(dctab, temp, index);
if (value >= 0xFF00) {
return value;
}
prev[ctrC] = this.IDCT_Source[0] = prev[ctrC] + this.getn(index, value, temp, index);
this.IDCT_Source[0] *= qtab[0];
for (j = 1; j < 64; j+=1) {
value = this.getHuffmanValue(actab, temp, index);
if (value >= 0xFF00) {
return value;
}
j += (value >> 4);
if ((value & 0x0F) === 0) {
if ((value >> 4) === 0) {
break;
}
} else {
this.IDCT_Source[jpeg.lossless.Decoder.IDCT_P[j]] = this.getn(index, value & 0x0F, temp, index) * qtab[j];
}
}
}
}
return 0;
};
jpeg.lossless.Decoder.prototype.decodeSingle = function (prev, temp, index) {
/*jslint bitwise: true */
var value, i;
prev[0] = this.selector();
for (i = 0; i < this.nBlock[0]; i+=1) {
value = this.getHuffmanValue(this.dcTab[0], temp, index);
if (value >= 0xFF00) {
return value;
}
prev[0] += this.getn(prev, value, temp, index);
}
return 0;
};
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
// HuffTab[*][*][0-256] is always the only 1st-layer table.
//
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
//
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
// ):
// return: Huffman Value of table
// 0xFF?? if it receives a MARKER
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
// temp temp storage for remainded bits
// index index to bit of temp
// in FILE pointer
// Effect:
// temp store new remainded bits
// index change to new index
// in change to new position
// NOTE:
// Initial by temp=0; index=0;
// NOTE: (explain temp and index)
// temp: is always in the form at calling time or returning time
// | byte 4 | byte 3 | byte 2 | byte 1 |
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
// ^index=3 (from 0 to 15)
// 321
// NOTE (marker and marker_index):
// If get a MARKER from 'in', marker=the low-byte of the MARKER
// and marker_index=9
// If marker_index=9 then index is always > 8, or HuffmanValue()
// will not be called
jpeg.lossless.Decoder.prototype.getHuffmanValue = function (table, temp, index) {
/*jslint bitwise: true */
var code, input, mask;
mask = 0xFFFF;
if (index[0] < 8) {
temp[0] <<= 8;
input = this.stream.get8();
if (input === 0xFF) {
this.marker = this.stream.get8();
if (this.marker !== 0) {
this.markerIndex = 9;
}
}
temp[0] |= input;
} else {
index[0] -= 8;
}
code = table[temp[0] >> index[0]];
if ((code & jpeg.lossless.Decoder.MSB) !== 0) {
if (this.markerIndex !== 0) {
this.markerIndex = 0;
return 0xFF00 | this.marker;
}
temp[0] &= (mask >> (16 - index[0]));
temp[0] <<= 8;
input = this.stream.get8();
if (input === 0xFF) {
this.marker = this.stream.get8();
if (this.marker !== 0) {
this.markerIndex = 9;
}
}
temp[0] |= input;
code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])];
index[0] += 8;
}
index[0] += 8 - (code >> 8);
if (index[0] < 0) {
throw new Error("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()");
}
if (index[0] < this.markerIndex) {
this.markerIndex = 0;
return 0xFF00 | this.marker;
}
temp[0] &= (mask >> (16 - index[0]));
return code & 0xFF;
};
jpeg.lossless.Decoder.prototype.getn = function (PRED, n, temp, index) {
/*jslint bitwise: true */
var result, one, n_one, mask, input;
one = 1;
n_one = -1;
mask = 0xFFFF;
if (n === 0) {
return 0;
}
if (n === 16) {
if (PRED[0] >= 0) {
return -32768;
} else {
return 32768;
}
}
index[0] -= n;
if (index[0] >= 0) {
if ((index[0] < this.markerIndex) && !this.isLastPixel()) { // this was corrupting the last pixel in some cases
this.markerIndex = 0;
return (0xFF00 | this.marker) << 8;
}
result = temp[0] >> index[0];
temp[0] &= (mask >> (16 - index[0]));
} else {
temp[0] <<= 8;
input = this.stream.get8();
if (input === 0xFF) {
this.marker = this.stream.get8();
if (this.marker !== 0) {
this.markerIndex = 9;
}
}
temp[0] |= input;
index[0] += 8;
if (index[0] < 0) {
if (this.markerIndex !== 0) {
this.markerIndex = 0;
return (0xFF00 | this.marker) << 8;
}
temp[0] <<= 8;
input = this.stream.get8();
if (input === 0xFF) {
this.marker = this.stream.get8();
if (this.marker !== 0) {
this.markerIndex = 9;
}
}
temp[0] |= input;
index[0] += 8;
}
if (index[0] < 0) {
throw new Error("index=" + index[0] + " in getn()");
}
if (index[0] < this.markerIndex) {
this.markerIndex = 0;
return (0xFF00 | this.marker) << 8;
}
result = temp[0] >> index[0];
temp[0] &= (mask >> (16 - index[0]));
}
if (result < (one << (n - 1))) {
result += (n_one << n) + 1;
}
return result;
};
jpeg.lossless.Decoder.prototype.getPreviousX = function (compOffset) {
/*jslint bitwise: true */
if (this.xLoc > 0) {
return this.getter((((this.yLoc * this.xDim) + this.xLoc) - 1), compOffset);
} else if (this.yLoc > 0) {
return this.getPreviousY(compOffset);
} else {
return (1 << (this.frame.precision - 1));
}
};
jpeg.lossless.Decoder.prototype.getPreviousXY = function (compOffset) {
/*jslint bitwise: true */
if ((this.xLoc > 0) && (this.yLoc > 0)) {
return this.getter(((((this.yLoc - 1) * this.xDim) + this.xLoc) - 1), compOffset);
} else {
return this.getPreviousY(compOffset);
}
};
jpeg.lossless.Decoder.prototype.getPreviousY = function (compOffset) {
/*jslint bitwise: true */
if (this.yLoc > 0) {
return this.getter((((this.yLoc - 1) * this.xDim) + this.xLoc), compOffset);
} else {
return this.getPreviousX(compOffset);
}
};
jpeg.lossless.Decoder.prototype.isLastPixel = function () {
return (this.xLoc === (this.xDim - 1)) && (this.yLoc === (this.yDim - 1));
};
jpeg.lossless.Decoder.prototype.outputSingle = function (PRED) {
if ((this.xLoc < this.xDim) && (this.yLoc < this.yDim)) {
this.setter((((this.yLoc * this.xDim) + this.xLoc)), PRED[0]);
this.xLoc+=1;
if (this.xLoc >= this.xDim) {
this.yLoc+=1;
this.xLoc = 0;
}
}
};
jpeg.lossless.Decoder.prototype.outputRGB = function (PRED) {
var offset = ((this.yLoc * this.xDim) + this.xLoc);
if ((this.xLoc < this.xDim) && (this.yLoc < this.yDim)) {
this.setter(offset, PRED[0], 0);
this.setter(offset, PRED[1], 1);
this.setter(offset, PRED[2], 2);
this.xLoc+=1;
if (this.xLoc >= this.xDim) {
this.yLoc+=1;
this.xLoc = 0;
}
}
};
jpeg.lossless.Decoder.prototype.setValue16 = function (index, val) {
this.outputData.setInt16(index * 2, val, true);
};
jpeg.lossless.Decoder.prototype.getValue16 = function (index) {
return this.outputData.getInt16(index * 2, true);
};
jpeg.lossless.Decoder.prototype.setValue8 = function (index, val) {
this.outputData.setInt8(index, val);
};
jpeg.lossless.Decoder.prototype.getValue8 = function (index) {
return this.outputData.getInt8(index);
};
jpeg.lossless.Decoder.prototype.setValueRGB = function (index, val, compOffset) {
this.outputData.setUint8(index * 3 + compOffset, val);
};
jpeg.lossless.Decoder.prototype.getValueRGB = function (index, compOffset) {
return this.outputData.getUint8(index * 3 + compOffset);
};
jpeg.lossless.Decoder.prototype.readApp = function() {
var count = 0, length = this.stream.get16();
count += 2;
while (count < length) {
this.stream.get8();
count+=1;
}
return length;
};
jpeg.lossless.Decoder.prototype.readComment = function () {
var sb = "", count = 0, length;
length = this.stream.get16();
count += 2;
while (count < length) {
sb += this.stream.get8();
count+=1;
}
return sb;
};
jpeg.lossless.Decoder.prototype.readNumber = function() {
var Ld = this.stream.get16();
if (Ld !== 4) {
throw new Error("ERROR: Define number format throw new IOException [Ld!=4]");
}
return this.stream.get16();
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.Decoder;
}
},{"./data-stream.js":2,"./frame-header.js":4,"./huffman-table.js":5,"./quantization-table.js":7,"./scan-header.js":9,"./utils.js":10}],4:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
jpeg.lossless.ComponentSpec = jpeg.lossless.ComponentSpec || ((typeof require !== 'undefined') ? require('./component-spec.js') : null);
jpeg.lossless.DataStream = jpeg.lossless.DataStream || ((typeof require !== 'undefined') ? require('./data-stream.js') : null);
/*** Constructor ***/
jpeg.lossless.FrameHeader = jpeg.lossless.FrameHeader || function () {
this.components = []; // Components
this.dimX = 0; // Number of samples per line
this.dimY = 0; // Number of lines
this.numComp = 0; // Number of component in the frame
this.precision = 0; // Sample Precision (from the original image)
};
/*** Prototype Methods ***/
jpeg.lossless.FrameHeader.prototype.read = function (data) {
/*jslint bitwise: true */
var count = 0, length, i, c, temp;
length = data.get16();
count += 2;
this.precision = data.get8();
count+=1;
this.dimY = data.get16();
count += 2;
this.dimX = data.get16();
count += 2;
this.numComp = data.get8();
count+=1;
for (i = 1; i <= this.numComp; i+=1) {
if (count > length) {
throw new Error("ERROR: frame format error");
}
c = data.get8();
count+=1;
if (count >= length) {
throw new Error("ERROR: frame format error [c>=Lf]");
}
temp = data.get8();
count+=1;
if (!this.components[c]) {
this.components[c] = new jpeg.lossless.ComponentSpec();
}
this.components[c].hSamp = temp >> 4;
this.components[c].vSamp = temp & 0x0F;
this.components[c].quantTableSel = data.get8();
count+=1;
}
if (count !== length) {
throw new Error("ERROR: frame format error [Lf!=count]");
}
return 1;
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.FrameHeader;
}
},{"./component-spec.js":1,"./data-stream.js":2}],5:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
jpeg.lossless.DataStream = jpeg.lossless.DataStream || ((typeof require !== 'undefined') ? require('./data-stream.js') : null);
jpeg.lossless.Utils = jpeg.lossless.Utils || ((typeof require !== 'undefined') ? require('./utils.js') : null);
/*** Constructor ***/
jpeg.lossless.HuffmanTable = jpeg.lossless.HuffmanTable || function () {
this.l = jpeg.lossless.Utils.createArray(4, 2, 16);
this.th = [];
this.v = jpeg.lossless.Utils.createArray(4, 2, 16, 200);
this.tc = jpeg.lossless.Utils.createArray(4, 2);
this.tc[0][0] = 0;
this.tc[1][0] = 0;
this.tc[2][0] = 0;
this.tc[3][0] = 0;
this.tc[0][1] = 0;
this.tc[1][1] = 0;
this.tc[2][1] = 0;
this.tc[3][1] = 0;
this.th[0] = 0;
this.th[1] = 0;
this.th[2] = 0;
this.th[3] = 0;
};
/*** Static Pseudo-constants ***/
jpeg.lossless.HuffmanTable.MSB = 0x80000000;
/*** Prototype Methods ***/
jpeg.lossless.HuffmanTable.prototype.read = function(data, HuffTab) {
/*jslint bitwise: true */
var count = 0, length, temp, t, c, i, j;
length = data.get16();
count += 2;
while (count < length) {
temp = data.get8();
count+=1;
t = temp & 0x0F;
if (t > 3) {
throw new Error("ERROR: Huffman table ID > 3");
}
c = temp >> 4;
if (c > 2) {
throw new Error("ERROR: Huffman table [Table class > 2 ]");
}
this.th[t] = 1;
this.tc[t][c] = 1;
for (i = 0; i < 16; i+=1) {
this.l[t][c][i] = data.get8();
count+=1;
}
for (i = 0; i < 16; i+=1) {
for (j = 0; j < this.l[t][c][i]; j+=1) {
if (count > length) {
throw new Error("ERROR: Huffman table format error [count>Lh]");
}
this.v[t][c][i][j] = data.get8();
count+=1;
}
}
}
if (count !== length) {
throw new Error("ERROR: Huffman table format error [count!=Lf]");
}
for (i = 0; i < 4; i+=1) {
for (j = 0; j < 2; j+=1) {
if (this.tc[i][j] !== 0) {
this.buildHuffTable(HuffTab[i][j], this.l[i][j], this.v[i][j]);
}
}
}
return 1;
};
// Build_HuffTab()
// Parameter: t table ID
// c table class ( 0 for DC, 1 for AC )
// L[i] # of codewords which length is i
// V[i][j] Huffman Value (length=i)
// Effect:
// build up HuffTab[t][c] using L and V.
jpeg.lossless.HuffmanTable.prototype.buildHuffTable = function(tab, L, V) {
/*jslint bitwise: true */
var currentTable, temp, k, i, j, n;
temp = 256;
k = 0;
for (i = 0; i < 8; i+=1) { // i+1 is Code length
for (j = 0; j < L[i]; j+=1) {
for (n = 0; n < (temp >> (i + 1)); n+=1) {
tab[k] = V[i][j] | ((i + 1) << 8);
k+=1;
}
}
}
for (i = 1; k < 256; i+=1, k+=1) {
tab[k] = i | jpeg.lossless.HuffmanTable.MSB;
}
currentTable = 1;
k = 0;
for (i = 8; i < 16; i+=1) { // i+1 is Code length
for (j = 0; j < L[i]; j+=1) {
for (n = 0; n < (temp >> (i - 7)); n+=1) {
tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8);
k+=1;
}
if (k >= 256) {
if (k > 256) {
throw new Error("ERROR: Huffman table error(1)!");
}
k = 0;
currentTable+=1;
}
}
}
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.HuffmanTable;
}
},{"./data-stream.js":2,"./utils.js":10}],6:[function(require,module,exports){
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
jpeg.lossless.ComponentSpec = jpeg.lossless.ComponentSpec || ((typeof require !== 'undefined') ? require('./component-spec.js') : null);
jpeg.lossless.DataStream = jpeg.lossless.DataStream || ((typeof require !== 'undefined') ? require('./data-stream.js') : null);
jpeg.lossless.Decoder = jpeg.lossless.Decoder || ((typeof require !== 'undefined') ? require('./decoder.js') : null);
jpeg.lossless.FrameHeader = jpeg.lossless.FrameHeader || ((typeof require !== 'undefined') ? require('./frame-header.js') : null);
jpeg.lossless.HuffmanTable = jpeg.lossless.HuffmanTable || ((typeof require !== 'undefined') ? require('./huffman-table.js') : null);
jpeg.lossless.QuantizationTable = jpeg.lossless.QuantizationTable || ((typeof require !== 'undefined') ? require('./quantization-table.js') : null);
jpeg.lossless.ScanComponent = jpeg.lossless.ScanComponent || ((typeof require !== 'undefined') ? require('./scan-component.js') : null);
jpeg.lossless.ScanHeader = jpeg.lossless.ScanHeader || ((typeof require !== 'undefined') ? require('./scan-header.js') : null);
jpeg.lossless.Utils = jpeg.lossless.Utils || ((typeof require !== 'undefined') ? require('./utils.js') : null);
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg;
}
},{"./component-spec.js":1,"./data-stream.js":2,"./decoder.js":3,"./frame-header.js":4,"./huffman-table.js":5,"./quantization-table.js":7,"./scan-component.js":8,"./scan-header.js":9,"./utils.js":10}],7:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
jpeg.lossless.DataStream = jpeg.lossless.DataStream || ((typeof require !== 'undefined') ? require('./data-stream.js') : null);
jpeg.lossless.Utils = jpeg.lossless.Utils || ((typeof require !== 'undefined') ? require('./utils.js') : null);
/*** Constructor ***/
jpeg.lossless.QuantizationTable = jpeg.lossless.QuantizationTable || function () {
this.precision = []; // Quantization precision 8 or 16
this.tq = []; // 1: this table is presented
this.quantTables = jpeg.lossless.Utils.createArray(4, 64); // Tables
this.tq[0] = 0;
this.tq[1] = 0;
this.tq[2] = 0;
this.tq[3] = 0;
};
/*** Static Methods ***/
jpeg.lossless.QuantizationTable.enhanceQuantizationTable = function(qtab, table) {
/*jslint bitwise: true */
var i;
for (i = 0; i < 8; i+=1) {
qtab[table[(0 * 8) + i]] *= 90;
qtab[table[(4 * 8) + i]] *= 90;
qtab[table[(2 * 8) + i]] *= 118;
qtab[table[(6 * 8) + i]] *= 49;
qtab[table[(5 * 8) + i]] *= 71;
qtab[table[(1 * 8) + i]] *= 126;
qtab[table[(7 * 8) + i]] *= 25;
qtab[table[(3 * 8) + i]] *= 106;
}
for (i = 0; i < 8; i+=1) {
qtab[table[0 + (8 * i)]] *= 90;
qtab[table[4 + (8 * i)]] *= 90;
qtab[table[2 + (8 * i)]] *= 118;
qtab[table[6 + (8 * i)]] *= 49;
qtab[table[5 + (8 * i)]] *= 71;
qtab[table[1 + (8 * i)]] *= 126;
qtab[table[7 + (8 * i)]] *= 25;
qtab[table[3 + (8 * i)]] *= 106;
}
for (i = 0; i < 64; i+=1) {
qtab[i] >>= 6;
}
};
/*** Prototype Methods ***/
jpeg.lossless.QuantizationTable.prototype.read = function (data, table) {
/*jslint bitwise: true */
var count = 0, length, temp, t, i;
length = data.get16();
count += 2;
while (count < length) {
temp = data.get8();
count+=1;
t = temp & 0x0F;
if (t > 3) {
throw new Error("ERROR: Quantization table ID > 3");
}
this.precision[t] = temp >> 4;
if (this.precision[t] === 0) {
this.precision[t] = 8;
} else if (this.precision[t] === 1) {
this.precision[t] = 16;
} else {
throw new Error("ERROR: Quantization table precision error");
}
this.tq[t] = 1;
if (this.precision[t] === 8) {
for (i = 0; i < 64; i+=1) {
if (count > length) {
throw new Error("ERROR: Quantization table format error");
}
this.quantTables[t][i] = data.get8();
count+=1;
}
jpeg.lossless.QuantizationTable.enhanceQuantizationTable(this.quantTables[t], table);
} else {
for (i = 0; i < 64; i+=1) {
if (count > length) {
throw new Error("ERROR: Quantization table format error");
}
this.quantTables[t][i] = data.get16();
count += 2;
}
jpeg.lossless.QuantizationTable.enhanceQuantizationTable(this.quantTables[t], table);
}
}
if (count !== length) {
throw new Error("ERROR: Quantization table error [count!=Lq]");
}
return 1;
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.QuantizationTable;
}
},{"./data-stream.js":2,"./utils.js":10}],8:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
/*** Constructor ***/
jpeg.lossless.ScanComponent = jpeg.lossless.ScanComponent || function () {
this.acTabSel = 0; // AC table selector
this.dcTabSel = 0; // DC table selector
this.scanCompSel = 0; // Scan component selector
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.ScanComponent;
}
},{}],9:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
jpeg.lossless.DataStream = jpeg.lossless.DataStream || ((typeof require !== 'undefined') ? require('./data-stream.js') : null);
jpeg.lossless.ScanComponent = jpeg.lossless.ScanComponent || ((typeof require !== 'undefined') ? require('./scan-component.js') : null);
/*** Constructor ***/
jpeg.lossless.ScanHeader = jpeg.lossless.ScanHeader || function () {
this.ah = 0;
this.al = 0;
this.numComp = 0; // Number of components in the scan
this.selection = 0; // Start of spectral or predictor selection
this.spectralEnd = 0; // End of spectral selection
this.components = [];
};
/*** Prototype Methods ***/
jpeg.lossless.ScanHeader.prototype.read = function(data) {
/*jslint bitwise: true */
var count = 0, length, i, temp;
length = data.get16();
count += 2;
this.numComp = data.get8();
count+=1;
for (i = 0; i < this.numComp; i+=1) {
this.components[i] = new jpeg.lossless.ScanComponent();
if (count > length) {
throw new Error("ERROR: scan header format error");
}
this.components[i].scanCompSel = data.get8();
count+=1;
temp = data.get8();
count+=1;
this.components[i].dcTabSel = (temp >> 4);
this.components[i].acTabSel = (temp & 0x0F);
}
this.selection = data.get8();
count+=1;
this.spectralEnd = data.get8();
count+=1;
temp = data.get8();
this.ah = (temp >> 4);
this.al = (temp & 0x0F);
count+=1;
if (count !== length) {
throw new Error("ERROR: scan header format error [count!=Ns]");
}
return 1;
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.ScanHeader;
}
},{"./data-stream.js":2,"./scan-component.js":8}],10:[function(require,module,exports){
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* 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.
*/
/*jslint browser: true, node: true */
/*global require, module */
"use strict";
/*** Imports ***/
var jpeg = jpeg || {};
jpeg.lossless = jpeg.lossless || {};
/*** Constructor ***/
jpeg.lossless.Utils = jpeg.lossless.Utils || {};
/*** Static methods ***/
// http://stackoverflow.com/questions/966225/how-can-i-create-a-two-dimensional-array-in-javascript
jpeg.lossless.Utils.createArray = function (length) {
var arr = new Array(length || 0),
i = length;
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
while(i--) arr[length-1 - i] = jpeg.lossless.Utils.createArray.apply(this, args);
}
return arr;
};
/*** Exports ***/
var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
module.exports = jpeg.lossless.Utils;
}
},{}]},{},[6])(6)
});
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function extractUncompressedPixels(dataSet, frame)
{
var pixelFormat = cornerstoneWADOImageLoader.getPixelFormat(dataSet);
var pixelDataElement = dataSet.elements.x7fe00010;
var height = dataSet.uint16('x00280010');
var width = dataSet.uint16('x00280011');
var samplesPerPixel = dataSet.uint16('x00280002');
var pixelDataOffset = pixelDataElement.dataOffset;
var numPixels = width * height * samplesPerPixel;
// Note - we may want to sanity check the rows * columns * bitsAllocated * samplesPerPixel against the buffer size
var frameOffset = 0;
if(pixelFormat === 1) {
frameOffset = pixelDataOffset + frame * numPixels;
return new Uint8Array(dataSet.byteArray.buffer, frameOffset, numPixels);
}
else if(pixelFormat === 2) {
frameOffset = pixelDataOffset + frame * numPixels * 2;
return new Uint16Array(dataSet.byteArray.buffer, frameOffset, numPixels);
}
else if(pixelFormat === 3) {
frameOffset = pixelDataOffset + frame * numPixels * 2;
return new Int16Array(dataSet.byteArray.buffer, frameOffset, numPixels);
}
}
cornerstoneWADOImageLoader.extractUncompressedPixels = extractUncompressedPixels;
}($, cornerstone, cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function loadImage(imageId) {
// create a deferred object
var deferred = $.Deferred();
// build a url by parsing out the url scheme and frame index from the imageId
var firstColonIndex = imageId.indexOf(':');
var url = imageId.substring(firstColonIndex + 1);
var frameIndex = url.indexOf('frame=');
var frame;
if(frameIndex !== -1) {
var frameStr = url.substr(frameIndex + 6);
frame = parseInt(frameStr);
url = url.substr(0, frameIndex-1);
}
// if multiframe and cached, use the cached data set to extract the frame
if(frame !== undefined &&
cornerstoneWADOImageLoader.internal.multiFrameCacheHack.hasOwnProperty(url))
{
var dataSet = cornerstoneWADOImageLoader.internal.multiFrameCacheHack[url];
var imagePromise = cornerstoneWADOImageLoader.createImageObject(dataSet, imageId, frame);
imagePromise.then(function(image) {
deferred.resolve(image);
}, function(error) {
deferred.reject(error);
});
return deferred;
}
var fileIndex = parseInt(url);
var file = cornerstoneWADOImageLoader.fileManager.get(fileIndex);
if(file === undefined) {
deferred.reject('unknown file index ' + url);
return deferred;
}
var fileReader = new FileReader();
fileReader.onload = function(e) {
// Parse the DICOM File
var dicomPart10AsArrayBuffer = e.target.result;
var byteArray = new Uint8Array(dicomPart10AsArrayBuffer);
var dataSet = dicomParser.parseDicom(byteArray);
// if multiframe, cache the parsed data set to speed up subsequent
// requests for the other frames
if(frame !== undefined) {
var dataSet = cornerstoneWADOImageLoader.internal.multiFrameCacheHack[url];
var imagePromise = cornerstoneWADOImageLoader.createImageObject(dataSet, imageId, frame);
imagePromise.then(function(image) {
deferred.resolve(image);
}, function(error) {
deferred.reject(error);
});
return deferred;
}
var imagePromise = cornerstoneWADOImageLoader.createImageObject(dataSet, imageId, frame);
imagePromise.then(function(image) {
deferred.resolve(image);
}, function() {
deferred.reject();
});
};
fileReader.readAsArrayBuffer(file);
return deferred;
}
// registery dicomweb and wadouri image loader prefixes
cornerstone.registerImageLoader('dicomfile', loadImage);
}($, cornerstone, cornerstoneWADOImageLoader));
/**
*/
(function (cornerstoneWADOImageLoader) {
"use strict";
var files = [];
function add(file) {
var fileIndex = files.push(file);
return 'dicomfile:' + (fileIndex - 1);
}
function get(index) {
return files[index];
}
function remove(index) {
files[index] = undefined;
}
function purge() {
files = [];
}
// module exports
cornerstoneWADOImageLoader.fileManager = {
add : add,
get : get,
remove:remove,
purge: purge
};
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function getMinMax(storedPixelData)
{
// we always calculate the min max values since they are not always
// present in DICOM and we don't want to trust them anyway as cornerstone
// depends on us providing reliable values for these
var min = 65535;
var max = -32768;
var numPixels = storedPixelData.length;
var pixelData = storedPixelData;
for(var index = 0; index < numPixels; index++) {
var spv = pixelData[index];
// TODO: test to see if it is faster to use conditional here rather than calling min/max functions
min = Math.min(min, spv);
max = Math.max(max, spv);
}
return {
min: min,
max: max
};
}
// module exports
cornerstoneWADOImageLoader.getMinMax = getMinMax;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function getPixelFormat(dataSet) {
var pixelRepresentation = dataSet.uint16('x00280103');
var bitsAllocated = dataSet.uint16('x00280100');
if(pixelRepresentation === 0 && bitsAllocated === 8) {
return 1; // unsigned 8 bit
} else if(pixelRepresentation === 0 && bitsAllocated === 16) {
return 2; // unsigned 16 bit
} else if(pixelRepresentation === 1 && bitsAllocated === 16) {
return 3; // signed 16 bit data
}
}
// module exports
cornerstoneWADOImageLoader.getPixelFormat = getPixelFormat;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function getPixelSpacing(dataSet)
{
// NOTE - these are not required for all SOP Classes
// so we return them as undefined. We also do not
// deal with the complexity associated with projection
// radiographs here and leave that to a higher layer
var pixelSpacing = dataSet.string('x00280030');
if(pixelSpacing && pixelSpacing.length > 0) {
var split = pixelSpacing.split('\\');
return {
row: parseFloat(split[0]),
column: parseFloat(split[1])
};
}
else {
return {
row: undefined,
column: undefined
};
}
}
// module exports
cornerstoneWADOImageLoader.getPixelSpacing = getPixelSpacing;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function getRescaleSlopeAndIntercept(dataSet)
{
// NOTE - we default these to an identity transform since modality LUT
// module is not required for all SOP Classes
var result = {
intercept : 0.0,
slope: 1.0
};
if(dataSet.elements.x00281052 && dataSet.elements.x00281053) {
result.intercept = dataSet.floatString('x00281052');
result.slope = dataSet.floatString('x00281053');
}
return result;
}
// module exports
cornerstoneWADOImageLoader.getRescaleSlopeAndIntercept = getRescaleSlopeAndIntercept;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function getWindowWidthAndCenter(dataSet)
{
// NOTE - Default these to undefined since they may not be present as
// they are not present or required for all sop classes. We leave it up
// to a higher layer to determine reasonable default values for these
// if they are not provided. We also use the first ww/wc values if
// there are multiple and again leave it up the higher levels to deal with
// this
var result = {
windowCenter : undefined,
windowWidth: undefined
};
if(dataSet.elements.x00281050 && dataSet.elements.x00281051) {
result.windowCenter = dataSet.floatString('x00281050');
result.windowWidth = dataSet.floatString('x00281051');
}
return result;
}
// module exports
cornerstoneWADOImageLoader.getWindowWidthAndCenter = getWindowWidthAndCenter;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
var options = {
// callback allowing customization of the xhr (e.g. adding custom auth headers, cors, etc)
beforeSend : function(xhr) {}
};
function configure(opts) {
options = opts;
}
function isColorImage(photoMetricInterpretation)
{
if(photoMetricInterpretation === "RGB" ||
photoMetricInterpretation === "PALETTE COLOR" ||
photoMetricInterpretation === "YBR_FULL" ||
photoMetricInterpretation === "YBR_FULL_422" ||
photoMetricInterpretation === "YBR_PARTIAL_422" ||
photoMetricInterpretation === "YBR_PARTIAL_420" ||
photoMetricInterpretation === "YBR_RCT" ||
photoMetricInterpretation === "YBR_ICT")
{
return true;
}
else
{
return false;
}
}
cornerstoneWADOImageLoader.isColorImage = isColorImage;
}(cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
var canvas = document.createElement('canvas');
var lastImageIdDrawn = "";
function extractStoredPixels(dataSet, frame) {
// special case for JPEG Baseline 8 bit
if(cornerstoneWADOImageLoader.isJPEGBaseline8Bit(dataSet) === true)
{
return cornerstoneWADOImageLoader.decodeJPEGBaseline8Bit(canvas, dataSet, frame);
}
var decodedImageFrame = cornerstoneWADOImageLoader.decodeTransferSyntax(dataSet, frame);
return cornerstoneWADOImageLoader.convertColorSpace(canvas, dataSet, decodedImageFrame);
}
function makeColorImage(imageId, dataSet, frame) {
// extract the DICOM attributes we need
var pixelSpacing = cornerstoneWADOImageLoader.getPixelSpacing(dataSet);
var rows = dataSet.uint16('x00280010');
var columns = dataSet.uint16('x00280011');
var rescaleSlopeAndIntercept = cornerstoneWADOImageLoader.getRescaleSlopeAndIntercept(dataSet);
var bytesPerPixel = 4;
var numPixels = rows * columns;
var sizeInBytes = numPixels * bytesPerPixel;
var windowWidthAndCenter = cornerstoneWADOImageLoader.getWindowWidthAndCenter(dataSet);
// clear the lastImageIdDrawn so we update the canvas
lastImageIdDrawn = undefined;
var deferred = $.Deferred();
// Decompress and decode the pixel data for this image
var imageDataPromise;
try {
imageDataPromise = extractStoredPixels(dataSet, frame);
}
catch(err) {
deferred.reject(err);
return deferred;
}
imageDataPromise.then(function(imageData) {
function getPixelData() {
return imageData.data;
}
function getImageData() {
return imageData;
}
function getCanvas() {
if(lastImageIdDrawn === imageId) {
return canvas;
}
canvas.height = rows;
canvas.width = columns;
var context = canvas.getContext('2d');
context.putImageData(imageData, 0, 0 );
lastImageIdDrawn = imageId;
return canvas;
}
// Extract the various attributes we need
var image = {
imageId : imageId,
minPixelValue : 0,
maxPixelValue : 255,
slope: rescaleSlopeAndIntercept.slope,
intercept: rescaleSlopeAndIntercept.intercept,
windowCenter : windowWidthAndCenter.windowCenter,
windowWidth : windowWidthAndCenter.windowWidth,
render: cornerstone.renderColorImage,
getPixelData: getPixelData,
getImageData: getImageData,
getCanvas: getCanvas,
rows: rows,
columns: columns,
height: rows,
width: columns,
color: true,
columnPixelSpacing: pixelSpacing.column,
rowPixelSpacing: pixelSpacing.row,
data: dataSet,
invert: false,
sizeInBytes: sizeInBytes
};
if(image.windowCenter === undefined) {
image.windowWidth = 255;
image.windowCenter = 128;
}
deferred.resolve(image);
}, function(error) {
deferred.reject(error);
});
return deferred;
}
// module exports
cornerstoneWADOImageLoader.makeColorImage = makeColorImage;
}($, cornerstone, cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function getBytesPerPixel(dataSet)
{
var pixelFormat = cornerstoneWADOImageLoader.getPixelFormat(dataSet);
if(pixelFormat ===1) {
return 1;
}
else if(pixelFormat ===2 || pixelFormat ===3){
return 2;
}
throw "unknown pixel format";
}
function getLUT(image, pixelRepresentation, lutDataSet) {
var numLUTEntries = lutDataSet.uint16('x00283002', 0);
if(numLUTEntries === 0) {
numLUTEntries = 65535;
}
var firstValueMapped = 0;
if(pixelRepresentation === 0) {
firstValueMapped = lutDataSet.uint16('x00283002', 1);
} else {
firstValueMapped = lutDataSet.int16('x00283002', 1);
}
var numBitsPerEntry = lutDataSet.uint16('x00283002', 2);
//console.log('LUT(', numLUTEntries, ',', firstValueMapped, ',', numBitsPerEntry, ')');
var lut = {
id : '1',
firstValueMapped: firstValueMapped,
numBitsPerEntry : numBitsPerEntry,
lut : []
};
//console.log("minValue=", minValue, "; maxValue=", maxValue);
for (var i = 0; i < numLUTEntries; i++) {
if(pixelRepresentation === 0) {
lut.lut[i] = lutDataSet.uint16('x00283006', i);
} else {
lut.lut[i] = lutDataSet.int16('x00283006', i);
}
}
return lut;
}
function makeGrayscaleImage(imageId, dataSet, frame) {
var deferred = $.Deferred();
// extract the DICOM attributes we need
var pixelSpacing = cornerstoneWADOImageLoader.getPixelSpacing(dataSet);
var rows = dataSet.uint16('x00280010');
var columns = dataSet.uint16('x00280011');
var rescaleSlopeAndIntercept = cornerstoneWADOImageLoader.getRescaleSlopeAndIntercept(dataSet);
var bytesPerPixel;
try {
bytesPerPixel = getBytesPerPixel(dataSet);
} catch(error) {
deferred.reject(error);
return deferred;
}
var numPixels = rows * columns;
var sizeInBytes = numPixels * bytesPerPixel;
var photometricInterpretation = dataSet.string('x00280004');
var invert = (photometricInterpretation === "MONOCHROME1");
var windowWidthAndCenter = cornerstoneWADOImageLoader.getWindowWidthAndCenter(dataSet);
// Decompress and decode the pixel data for this image
var storedPixelData;
try {
storedPixelData = cornerstoneWADOImageLoader.decodeTransferSyntax(dataSet, frame);
}
catch(err) {
deferred.reject(err);
return deferred;
}
var minMax = cornerstoneWADOImageLoader.getMinMax(storedPixelData);
function getPixelData() {
return storedPixelData;
}
// Extract the various attributes we need
var image = {
imageId : imageId,
minPixelValue : minMax.min,
maxPixelValue : minMax.max,
slope: rescaleSlopeAndIntercept.slope,
intercept: rescaleSlopeAndIntercept.intercept,
windowCenter : windowWidthAndCenter.windowCenter,
windowWidth : windowWidthAndCenter.windowWidth,
render: cornerstone.renderGrayscaleImage,
getPixelData: getPixelData,
rows: rows,
columns: columns,
height: rows,
width: columns,
color: false,
columnPixelSpacing: pixelSpacing.column,
rowPixelSpacing: pixelSpacing.row,
data: dataSet,
invert: invert,
sizeInBytes: sizeInBytes
};
// modality LUT
var pixelRepresentation = dataSet.uint16('x00280103');
if(dataSet.elements.x00283000) {
image.modalityLUT = getLUT(image, pixelRepresentation, dataSet.elements.x00283000.items[0].dataSet);
}
// VOI LUT
if(dataSet.elements.x00283010) {
pixelRepresentation = 0;
// if modality LUT can produce negative values, the data is signed
if(image.minPixelValue * image.slope + image.intercept < 0) {
pixelRepresentation = 1;
}
image.voiLUT = getLUT(image, pixelRepresentation, dataSet.elements.x00283010.items[0].dataSet);
}
// TODO: deal with pixel padding and all of the various issues by setting it to min pixel value (or lower)
// TODO: Mask out overlays embedded in pixel data above high bit
if(image.windowCenter === undefined) {
var maxVoi = image.maxPixelValue * image.slope + image.intercept;
var minVoi = image.minPixelValue * image.slope + image.intercept;
image.windowWidth = maxVoi - minVoi;
image.windowCenter = (maxVoi + minVoi) / 2;
}
deferred.resolve(image);
return deferred;
}
// module exports
cornerstoneWADOImageLoader.makeGrayscaleImage = makeGrayscaleImage;
}($, cornerstone, cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
// module exports
cornerstoneWADOImageLoader.version = '0.7.2';
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
function checkToken(token, data, dataOffset) {
if(dataOffset + token.length > data.length) {
//console.log('dataOffset >> ', dataOffset);
return false;
}
var endIndex = dataOffset;
for(var i = 0; i < token.length; i++) {
if(token[i] !== data[endIndex++]) {
if(endIndex > 520000) {
console.log('token=',uint8ArrayToString(token));
console.log('data=', uint8ArrayToString(data, dataOffset, endIndex-dataOffset));
console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(data[endIndex]), endIndex);
console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(token[endIndex]), endIndex);
}
return false;
}
}
return true;
}
function stringToUint8Array(str) {
var uint=new Uint8Array(str.length);
for(var i=0,j=str.length;i<j;i++){
uint[i]=str.charCodeAt(i);
}
return uint;
}
function findIndexOfString(data, str, offset) {
offset = offset || 0;
var token = stringToUint8Array(str);
for(var i=offset; i < data.length; i++) {
if(token[0] === data[i]) {
//console.log('match @', i);
if(checkToken(token, data, i)) {
return i;
}
}
}
return -1;
}
cornerstoneWADOImageLoader.internal.findIndexOfString = findIndexOfString;
}(cornerstoneWADOImageLoader));
(function (cornerstoneWADOImageLoader) {
"use strict";
function findBoundary(header) {
for(var i=0; i < header.length; i++) {
if(header[i].substr(0,2) === '--') {
return header[i];
}
}
return undefined;
}
function findContentType(header) {
for(var i=0; i < header.length; i++) {
if(header[i].substr(0,13) === 'Content-Type:') {
return header[i].substr(13).trim();
}
}
return undefined;
}
function uint8ArrayToString(data, offset, length) {
offset = offset || 0;
length = length || data.length - offset;
var str = "";
for(var i=offset; i < offset + length; i++) {
str += String.fromCharCode(data[i]);
}
return str;
};
cornerstoneWADOImageLoader.internal.getImageFrame = function(uri, mediaType) {
mediaType = mediaType || 'application/octet-stream';
var deferred = $.Deferred();
var xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
xhr.open("get", uri, true);
xhr.setRequestHeader('Accept', 'multipart/related;type=' + mediaType);
xhr.onreadystatechange = function (oEvent) {
// TODO: consider sending out progress messages here as we receive the pixel data
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// request succeeded, Parse the multi-part mime response
var imageFrameAsArrayBuffer = xhr.response;
var response = new Uint8Array(xhr.response);
// First look for the multipart mime header
var tokenIndex = cornerstoneWADOImageLoader.internal.findIndexOfString(response, '\n\r\n');
if(tokenIndex === -1) {
deferred.reject('invalid response - no multipart mime header');
}
var header = uint8ArrayToString(response, 0, tokenIndex);
// Now find the boundary marker
var split = header.split('\r\n');
var boundary = findBoundary(split);
if(!boundary) {
deferred.reject('invalid response - no boundary marker')
}
var offset = tokenIndex + 4; // skip over the \n\r\n
// find the terminal boundary marker
var endIndex = cornerstoneWADOImageLoader.internal.findIndexOfString(response, boundary, offset);
if(endIndex === -1) {
deferred.reject('invalid response - terminating boundary not found');
}
// return the info for this pixel data
var length = endIndex - offset - 1;
deferred.resolve({
contentType: findContentType(split),
arrayBuffer: imageFrameAsArrayBuffer,
offset: offset,
length: length
});
}
else {
// request failed, reject the deferred
deferred.reject(xhr.response);
}
}
};
xhr.send();
return deferred;
};
}(cornerstoneWADOImageLoader));
/**
*/
(function (cornerstoneWADOImageLoader) {
"use strict";
var images = [];
function add(image) {
var fileIndex = images.push(image);
return 'wadors:' + (fileIndex - 1);
}
function get(index) {
return images[index];
}
function remove(index) {
images[index] = undefined;
}
function purge() {
images = [];
}
// module exports
cornerstoneWADOImageLoader.imageManager = {
add : add,
get : get,
remove:remove,
purge: purge
};
}(cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function loadImage(imageId) {
var deferred = $.Deferred();
var index = imageId.substring(7);
var image = cornerstoneWADOImageLoader.imageManager.get(index);
if(image === undefined) {
deferred.reject('unknown imageId');
return deferred;
}
var mediaType;// = 'image/dicom+jp2';
cornerstoneWADOImageLoader.internal.getImageFrame(image.uri, mediaType).then(function(result) {
//console.log(result);
// TODO: add support for retrieving compressed pixel data
var storedPixelData;
if(image.instance.bitsAllocated === 16) {
if(image.instance.pixelRepresentation === 0) {
storedPixelData = new Uint16Array(result.arrayBuffer, result.offset, result.length / 2);
} else {
storedPixelData = new Int16Array(result.arrayBuffer, result.offset, result.length / 2);
}
} else if(image.instance.bitsAllocated === 8) {
storedPixelData = new Uint8Array(result.arrayBuffer, result.offset, result.length);
}
// TODO: handle various color space conversions
var minMax = cornerstoneWADOImageLoader.getMinMax(storedPixelData);
image.imageId = imageId;
image.minPixelValue = minMax.min;
image.maxPixelValue = minMax.max;
image.render = cornerstone.renderGrayscaleImage;
image.getPixelData = function() {
return storedPixelData;
};
//console.log(image);
deferred.resolve(image);
}).fail(function(reason) {
deferred.reject(reason);
});
return deferred;
}
// registery dicomweb and wadouri image loader prefixes
cornerstone.registerImageLoader('wadors', loadImage);
}($, cornerstone, cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
// Loads an image given an imageId
// wado url example:
// http://localhost:3333/wado?requestType=WADO&studyUID=1.3.6.1.4.1.25403.166563008443.5076.20120418075541.1&seriesUID=1.3.6.1.4.1.25403.166563008443.5076.20120418075541.2&objectUID=1.3.6.1.4.1.25403.166563008443.5076.20120418075557.1&contentType=application%2Fdicom&transferSyntax=1.2.840.10008.1.2.1
// NOTE: supposedly the instance will be returned in Explicit Little Endian transfer syntax if you don't
// specify a transferSyntax but Osirix doesn't do this and seems to return it with the transfer syntax it is
// stored as.
function loadImage(imageId) {
// create a deferred object
// build a url by parsing out the url scheme and frame index from the imageId
var firstColonIndex = imageId.indexOf(':');
var url = imageId.substring(firstColonIndex + 1);
var frameIndex = url.indexOf('frame=');
var frame;
if(frameIndex !== -1) {
var frameStr = url.substr(frameIndex + 6);
frame = parseInt(frameStr);
url = url.substr(0, frameIndex-1);
}
// if multiframe and cached, use the cached data set to extract the frame
if(frame !== undefined &&
cornerstoneWADOImageLoader.internal.multiFrameCacheHack.hasOwnProperty(url))
{
var deferred = $.Deferred();
var dataSet = cornerstoneWADOImageLoader.internal.multiFrameCacheHack[url];
var imagePromise = cornerstoneWADOImageLoader.createImageObject(dataSet, imageId, frame);
imagePromise.then(function(image) {
deferred.resolve(image);
}, function(error) {
deferred.reject(error);
});
return deferred;
}
return cornerstoneWADOImageLoader.internal.xhrRequest(imageId, frame, url);
}
// registery dicomweb and wadouri image loader prefixes
cornerstone.registerImageLoader('dicomweb', loadImage);
cornerstone.registerImageLoader('wadouri', loadImage);
}($, cornerstone, cornerstoneWADOImageLoader));
(function ($, cornerstone, cornerstoneWADOImageLoader) {
"use strict";
function xhrRequest(imageId, frame, url) {
var deferred = $.Deferred();
// Make the request for the DICOM P10 SOP Instance
var xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "arraybuffer";
cornerstoneWADOImageLoader.internal.options.beforeSend(xhr);
xhr.onreadystatechange = function (oEvent) {
// TODO: consider sending out progress messages here as we receive the pixel data
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// request succeeded, create an image object and resolve the deferred
// Parse the DICOM File
var dicomPart10AsArrayBuffer = xhr.response;
var byteArray = new Uint8Array(dicomPart10AsArrayBuffer);
var dataSet = dicomParser.parseDicom(byteArray);
// if multiframe, cache the parsed data set to speed up subsequent
// requests for the other frames
if (frame !== undefined) {
cornerstoneWADOImageLoader.internal.multiFrameCacheHack[url] = dataSet;
}
var imagePromise = cornerstoneWADOImageLoader.createImageObject(dataSet, imageId, frame);
imagePromise.then(function (image) {
deferred.resolve(image);
}, function (error) {
deferred.reject(error);
});
}
else {
// request failed, reject the deferred
deferred.reject(xhr.response);
}
}
};
xhr.onprogress = function (oProgress) {
// console.log('progress:',oProgress)
if (oProgress.lengthComputable) { //evt.loaded the bytes browser receive
//evt.total the total bytes seted by the header
//
var loaded = oProgress.loaded;
var total = oProgress.total;
var percentComplete = Math.round((loaded / total) * 100);
$(cornerstone).trigger('CornerstoneImageLoadProgress', {
imageId: imageId,
loaded: loaded,
total: total,
percentComplete: percentComplete
});
}
};
xhr.send();
return deferred;
}
cornerstoneWADOImageLoader.internal.xhrRequest = xhrRequest;
}($, cornerstone, cornerstoneWADOImageLoader));