in src/core/jpg.js [898:1160]
markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) {
let 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)
const { appData, newOffset } = readDataBlock(data, offset);
offset = newOffset;
if (fileMarker === 0xffe0) {
// 'JFIF\x00'
if (
appData[0] === 0x4a &&
appData[1] === 0x46 &&
appData[2] === 0x49 &&
appData[3] === 0x46 &&
appData[4] === 0
) {
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) {
// 'Adobe'
if (
appData[0] === 0x41 &&
appData[1] === 0x64 &&
appData[2] === 0x6f &&
appData[3] === 0x62 &&
appData[4] === 0x65
) {
adobe = {
version: (appData[5] << 8) | appData[6],
flags0: (appData[7] << 8) | appData[8],
flags1: (appData[9] << 8) | appData[10],
transformCode: appData[11],
};
}
}
break;
case 0xffdb: // DQT (Define Quantization Tables)
const quantizationTablesLength = readUint16(data, offset);
offset += 2;
const quantizationTablesEnd = quantizationTablesLength + offset - 2;
let z;
while (offset < quantizationTablesEnd) {
const quantizationTableSpec = data[offset++];
const tableData = new Uint16Array(64);
if (quantizationTableSpec >> 4 === 0) {
// 8 bit values
for (j = 0; j < 64; j++) {
z = dctZigZag[j];
tableData[z] = data[offset++];
}
} else if (quantizationTableSpec >> 4 === 1) {
// 16 bit values
for (j = 0; j < 64; j++) {
z = dctZigZag[j];
tableData[z] = readUint16(data, offset);
offset += 2;
}
} else {
throw new JpegError("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 new JpegError("Only single frame JPEGs supported");
}
offset += 2; // Skip marker length.
frame = {};
frame.extended = fileMarker === 0xffc1;
frame.progressive = fileMarker === 0xffc2;
frame.precision = data[offset++];
const sofScanLines = readUint16(data, offset);
offset += 2;
frame.scanLines = dnlScanLines || sofScanLines;
frame.samplesPerLine = readUint16(data, offset);
offset += 2;
frame.components = [];
frame.componentIds = {};
const componentsCount = data[offset++];
let maxH = 0,
maxV = 0;
for (i = 0; i < componentsCount; i++) {
const componentId = data[offset];
const h = data[offset + 1] >> 4;
const v = data[offset + 1] & 15;
if (maxH < h) {
maxH = h;
}
if (maxV < v) {
maxV = v;
}
const qId = data[offset + 2];
l = frame.components.push({
h,
v,
quantizationId: qId,
quantizationTable: null, // See comment below.
});
frame.componentIds[componentId] = l - 1;
offset += 3;
}
frame.maxH = maxH;
frame.maxV = maxV;
prepareComponents(frame);
break;
case 0xffc4: // DHT (Define Huffman Tables)
const huffmanLength = readUint16(data, offset);
offset += 2;
for (i = 2; i < huffmanLength; ) {
const huffmanTableSpec = data[offset++];
const codeLengths = new Uint8Array(16);
let codeLengthSum = 0;
for (j = 0; j < 16; j++, offset++) {
codeLengthSum += codeLengths[j] = data[offset];
}
const huffmanValues = new Uint8Array(codeLengthSum);
for (j = 0; j < codeLengthSum; j++, offset++) {
huffmanValues[j] = data[offset];
}
i += 17 + codeLengthSum;
(huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[
huffmanTableSpec & 15
] = buildHuffmanTable(codeLengths, huffmanValues);
}
break;
case 0xffdd: // DRI (Define Restart Interval)
offset += 2; // Skip marker length.
resetInterval = readUint16(data, offset);
offset += 2;
break;
case 0xffda: // SOS (Start of Scan)
// A DNL marker (0xFFDC), if it exists, is only allowed at the end
// of the first scan segment and may only occur once in an image.
// Furthermore, to prevent an infinite loop, do *not* attempt to
// parse DNL markers during re-parsing of the JPEG scan data.
const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines;
offset += 2; // Skip marker length.
const selectorsCount = data[offset++],
components = [];
for (i = 0; i < selectorsCount; i++) {
const index = data[offset++];
const componentIndex = frame.componentIds[index];
const component = frame.components[componentIndex];
component.index = index;
const tableSpec = data[offset++];
component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
components.push(component);
}
const spectralStart = data[offset++],
spectralEnd = data[offset++],
successiveApproximation = data[offset++];
try {
const processed = decodeScan(
data,
offset,
frame,
components,
resetInterval,
spectralStart,
spectralEnd,
successiveApproximation >> 4,
successiveApproximation & 15,
parseDNLMarker
);
offset += processed;
} catch (ex) {
if (ex instanceof DNLMarkerError) {
warn(`${ex.message} -- attempting to re-parse the JPEG image.`);
return this.parse(data, { dnlScanLines: ex.scanLines });
} else if (ex instanceof EOIMarkerError) {
warn(`${ex.message} -- ignoring the rest of the image data.`);
break markerLoop;
}
throw ex;
}
break;
case 0xffdc: // DNL (Define Number of Lines)
// Ignore the marker, since it's being handled in `decodeScan`.
offset += 4;
break;
case 0xffff: // Fill bytes
if (data[offset] !== 0xff) {
// Avoid skipping a valid marker.
offset--;
}
break;
default:
// Could be incorrect encoding -- the last 0xFF byte of the previous
// block could have been eaten by the encoder, hence we fallback to
// `startPos = offset - 3` when looking for the next valid marker.
const nextFileMarker = findNextFileMarker(
data,
/* currentPos = */ offset - 2,
/* startPos = */ offset - 3
);
if (nextFileMarker?.invalid) {
warn(
"JpegImage.parse - unexpected data, current marker is: " +
nextFileMarker.invalid
);
offset = nextFileMarker.offset;
break;
}
if (!nextFileMarker || offset >= data.length - 1) {
warn(
"JpegImage.parse - reached the end of the image data " +
"without finding an EOI marker (0xFFD9)."
);
break markerLoop;
}
throw new JpegError(
"JpegImage.parse - unknown marker: " + fileMarker.toString(16)
);
}
fileMarker = readUint16(data, offset);
offset += 2;
}