From 64e5a7021ddb31594d0439ecf746e9c71916cbfe Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 11 Apr 2026 11:46:38 +0200 Subject: [PATCH] Re-factor the `JpegImage` class, and related code, to use `DataView`s when reading data --- src/core/jpg.js | 76 +++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/src/core/jpg.js b/src/core/jpg.js index c0985a351..d44b34b9b 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -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) {