Merge pull request #21086 from Snuffleupagus/JpegImage-DataView

Re-factor the `JpegImage` class, and related code, to use `DataView`s when reading data
This commit is contained in:
Jonas Jenwald 2026-04-11 17:34:39 +02:00 committed by GitHub
commit 5089cceec7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -17,7 +17,6 @@ import { assert, BaseException, warn } from "../shared/util.js";
import { ColorSpaceUtils } from "./colorspace_utils.js";
import { DeviceCmykCS } from "./colorspace.js";
import { grayToRGBA } from "../shared/image_utils.js";
import { readUint16 } from "./core_utils.js";
class JpegError extends BaseException {
constructor(msg) {
@ -122,6 +121,7 @@ function getBlockBufferOffset(component, row, col) {
function decodeScan(
data,
view,
offset,
frame,
components,
@ -151,7 +151,7 @@ function decodeScan(
if (nextByte === /* DNL = */ 0xdc && parseDNLMarker) {
offset += 2; // Skip marker length.
const scanLines = readUint16(data, offset);
const scanLines = view.getUint16(offset);
offset += 2;
if (scanLines > 0 && scanLines !== frame.scanLines) {
throw new DNLMarkerError(
@ -434,7 +434,7 @@ function decodeScan(
// find marker
bitsCount = 0;
fileMarker = findNextFileMarker(data, offset);
fileMarker = findNextFileMarker(data, view, offset);
if (!fileMarker) {
break; // Reached the end of the image data without finding any marker.
}
@ -717,14 +717,14 @@ function buildComponentData(frame, component) {
return component.blockData;
}
function findNextFileMarker(data, currentPos, startPos = currentPos) {
function findNextFileMarker(data, view, currentPos, startPos = currentPos) {
const maxPos = data.length - 1;
let newPos = startPos < currentPos ? startPos : currentPos;
if (currentPos >= maxPos) {
return null; // Don't attempt to read non-existent data and just return.
}
const currentMarker = readUint16(data, currentPos);
const currentMarker = view.getUint16(currentPos);
if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) {
return {
invalid: null,
@ -732,12 +732,12 @@ function findNextFileMarker(data, currentPos, startPos = currentPos) {
offset: currentPos,
};
}
let newMarker = readUint16(data, newPos);
let newMarker = view.getUint16(newPos);
while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) {
if (++newPos >= maxPos) {
return null; // Don't attempt to read non-existent data and just return.
}
newMarker = readUint16(data, newPos);
newMarker = view.getUint16(newPos);
}
return {
invalid: currentMarker.toString(16),
@ -769,12 +769,12 @@ function prepareComponents(frame) {
frame.mcusPerColumn = mcusPerColumn;
}
function readDataBlock(data, offset) {
const length = readUint16(data, offset);
function readDataBlock(data, view, offset) {
const length = view.getUint16(offset);
offset += 2;
let endOffset = offset + length - 2;
const fileMarker = findNextFileMarker(data, endOffset, offset);
const fileMarker = findNextFileMarker(data, view, endOffset, offset);
if (fileMarker?.invalid) {
warn(
"readDataBlock - incorrect length, current marker is: " +
@ -791,12 +791,12 @@ function readDataBlock(data, offset) {
};
}
function skipData(data, offset) {
const length = readUint16(data, offset);
function skipData(data, view, offset) {
const length = view.getUint16(offset);
offset += 2;
const endOffset = offset + length - 2;
const fileMarker = findNextFileMarker(data, endOffset, offset);
const fileMarker = findNextFileMarker(data, view, endOffset, offset);
if (fileMarker?.invalid) {
return fileMarker.offset;
}
@ -810,15 +810,16 @@ class JpegImage {
}
static canUseImageDecoder(data, colorTransform = -1) {
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let exifOffsets = null;
let offset = 0;
let numComponents = null;
let fileMarker = readUint16(data, offset);
let fileMarker = view.getUint16(offset);
offset += 2;
if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) {
throw new JpegError("SOI not found");
}
fileMarker = readUint16(data, offset);
fileMarker = view.getUint16(offset);
offset += 2;
markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) {
@ -826,7 +827,11 @@ class JpegImage {
case 0xffe1: // APP1 - Exif
// TODO: Remove this once https://github.com/w3c/webcodecs/issues/870
// is fixed.
const { appData, oldOffset, newOffset } = readDataBlock(data, offset);
const { appData, oldOffset, newOffset } = readDataBlock(
data,
view,
offset
);
offset = newOffset;
// 'Exif\x00\x00'
@ -845,7 +850,7 @@ class JpegImage {
// since that can modify the original PDF document.
exifOffsets = { exifStart: oldOffset + 6, exifEnd: newOffset };
}
fileMarker = readUint16(data, offset);
fileMarker = view.getUint16(offset);
offset += 2;
continue;
case 0xffc0: // SOF0 (Start of Frame, Baseline DCT)
@ -864,8 +869,8 @@ class JpegImage {
}
break;
}
offset = skipData(data, offset);
fileMarker = readUint16(data, offset);
offset = skipData(data, view, offset);
fileMarker = view.getUint16(offset);
offset += 2;
}
if (numComponents === 4) {
@ -878,6 +883,8 @@ class JpegImage {
}
parse(data, { dnlScanLines = null } = {}) {
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const maxOffset = data.length - 1;
let offset = 0;
let jfif = null;
let adobe = null;
@ -887,12 +894,12 @@ class JpegImage {
const huffmanTablesAC = [],
huffmanTablesDC = [];
let fileMarker = readUint16(data, offset);
let fileMarker = view.getUint16(offset);
offset += 2;
if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) {
throw new JpegError("SOI not found");
}
fileMarker = readUint16(data, offset);
fileMarker = view.getUint16(offset);
offset += 2;
markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) {
@ -915,7 +922,7 @@ class JpegImage {
case 0xffee: // APP14
case 0xffef: // APP15
case 0xfffe: // COM (Comment)
const { appData, newOffset } = readDataBlock(data, offset);
const { appData, newOffset } = readDataBlock(data, view, offset);
offset = newOffset;
if (fileMarker === 0xffe0) {
@ -962,7 +969,7 @@ class JpegImage {
break;
case 0xffdb: // DQT (Define Quantization Tables)
const quantizationTablesLength = readUint16(data, offset);
const quantizationTablesLength = view.getUint16(offset);
offset += 2;
const quantizationTablesEnd = quantizationTablesLength + offset - 2;
let z;
@ -979,7 +986,7 @@ class JpegImage {
// 16 bit values
for (j = 0; j < 64; j++) {
z = dctZigZag[j];
tableData[z] = readUint16(data, offset);
tableData[z] = view.getUint16(offset);
offset += 2;
}
} else {
@ -1001,10 +1008,10 @@ class JpegImage {
frame.extended = fileMarker === 0xffc1;
frame.progressive = fileMarker === 0xffc2;
frame.precision = data[offset++];
const sofScanLines = readUint16(data, offset);
const sofScanLines = view.getUint16(offset);
offset += 2;
frame.scanLines = dnlScanLines || sofScanLines;
frame.samplesPerLine = readUint16(data, offset);
frame.samplesPerLine = view.getUint16(offset);
offset += 2;
frame.components = [];
frame.componentIds = {};
@ -1037,7 +1044,7 @@ class JpegImage {
break;
case 0xffc4: // DHT (Define Huffman Tables)
const huffmanLength = readUint16(data, offset);
const huffmanLength = view.getUint16(offset);
offset += 2;
for (i = 2; i < huffmanLength; ) {
const huffmanTableSpec = data[offset++];
@ -1061,7 +1068,7 @@ class JpegImage {
case 0xffdd: // DRI (Define Restart Interval)
offset += 2; // Skip marker length.
resetInterval = readUint16(data, offset);
resetInterval = view.getUint16(offset);
offset += 2;
break;
@ -1092,6 +1099,7 @@ class JpegImage {
try {
const processed = decodeScan(
data,
view,
offset,
frame,
components,
@ -1133,6 +1141,7 @@ class JpegImage {
// `startPos = offset - 3` when looking for the next valid marker.
const nextFileMarker = findNextFileMarker(
data,
view,
/* currentPos = */ offset - 2,
/* startPos = */ offset - 3
);
@ -1144,7 +1153,7 @@ class JpegImage {
offset = nextFileMarker.offset;
break;
}
if (!nextFileMarker || offset >= data.length - 1) {
if (!nextFileMarker || offset >= maxOffset) {
warn(
"JpegImage.parse - reached the end of the image data " +
"without finding an EOI marker (0xFFD9)."
@ -1155,8 +1164,13 @@ class JpegImage {
"JpegImage.parse - unknown marker: " + fileMarker.toString(16)
);
}
fileMarker = readUint16(data, offset);
offset += 2;
if (offset < maxOffset) {
fileMarker = view.getUint16(offset);
offset += 2;
} else {
fileMarker = 0;
}
}
if (!frame) {