Skip to content

Commit

Permalink
Get image dimensions from the image element
Browse files Browse the repository at this point in the history
Also removed width/height query API

This fixes incorrect size for JPEGs using EXIF rotation, which wasn't
handled by the query API. The browser automatically rotates the image,
so the width/height could be different for display vs the frame header
  • Loading branch information
rcowsill committed Apr 21, 2024
1 parent 931d0c2 commit 079cc39
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 212 deletions.
5 changes: 2 additions & 3 deletions _site/components/sawmill_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function handleWheelEvent(e, { zoomLevel }, setZoom) {
}


function SawmillUI({ onFileInputChange, imageWidth, imageHeight, uint8Array, scanEndOffsets }) {
function SawmillUI({ onFileInputChange, uint8Array, scanEndOffsets }) {
const [brightness, onBrightnessSet] = useValueState(0);
const [diffView, onDiffViewSet, setDiffView] = useCheckedState(false);
const [duration, onDurationSet] = useValueState(10);
Expand Down Expand Up @@ -188,7 +188,6 @@ function SawmillUI({ onFileInputChange, imageWidth, imageHeight, uint8Array, sca
onFileInputChange
};

const imageDimensions = { width: imageWidth, height: imageHeight };
const viewerEvents = {
onAnimationEnd: (e) => handleAnimationEvent(e, animationIndex, scanData, playbackSetters),
onWheel: (e) => handleWheelEvent(e, zoom, setZoom)
Expand All @@ -197,7 +196,7 @@ function SawmillUI({ onFileInputChange, imageWidth, imageHeight, uint8Array, sca
return html`
<div class=sawmill-ui ref=${sawmillUIRef} tabindex=-1 ...${keyboardEvents}>
<${SawmillToolbar} ...${{ playback, toolbarDisabled, settings, zoomLevels, toolbarEvents }} />
<${SawmillViewer} ...${{ playback, scanData, selected, imageDimensions, settings, viewerEvents }} />
<${SawmillViewer} ...${{ playback, scanData, selected, settings, viewerEvents }} />
</div>
`;
}
Expand Down
4 changes: 2 additions & 2 deletions _site/components/sawmill_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function getViewerStyles(duration) {
}


function SawmillViewer({ playback, scanData=[], selected=0, imageDimensions, settings, viewerEvents }) {
function SawmillViewer({ playback, scanData=[], selected=0, settings, viewerEvents }) {
const { duration, scanlines, zoom } = settings;

const viewerProps = {
Expand Down Expand Up @@ -72,7 +72,7 @@ function SawmillViewer({ playback, scanData=[], selected=0, imageDimensions, set
<div ...${viewerProps}>
<${SawmillMeter} ...${meterProps} />
<div class=scroll-box ref=${scrollRef} onwheel=${viewerEvents.onWheel}>
<${SawmillViewerFilter} ...${{ scanData, selected, imageDimensions, settings }}/>
<${SawmillViewerFilter} ...${{ scanData, selected, settings }}/>
</div>
</div>
`;
Expand Down
52 changes: 46 additions & 6 deletions _site/components/sawmill_viewer_filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,23 @@
*/

import { html } from "../external/preact-htm-3.1.1.js";
import { useLayoutEffect, useRef, useState } from "../external/hooks.module.js";
import SawmillAboutBox from "./sawmill_about_box.js";


function getFilterClasses(settings) {
function getFilterClasses(imageDimensions, settings) {
const { brightness, diffView } = settings;
const { zoomLevel } = settings.zoom;

const classes = ["filter"];

if (diffView) { classes.push("difference"); }
if (brightness > 0) { classes.push("brightness"); }
if (zoomLevel > 1) { classes.push("magnified"); }
if (zoomLevel !== 1) { classes.push("zoomed"); }

if (zoomLevel !== 1 && imageDimensions !== null) {
classes.push("zoomed");
if (zoomLevel > 1) { classes.push("magnified"); }
}

return classes.join(" ");
}
Expand All @@ -44,7 +48,7 @@ function getFilterStyles(imageDimensions, scanData, settings) {
"--diffview-brightness": 2 ** brightness
};

if (zoomLevel !== 1) {
if (zoomLevel !== 1 && imageDimensions !== null) {
styles["--zoomed-img-width"] = `${zoomLevel * imageDimensions.width}px`;
styles["--zoomed-img-height"] = `${zoomLevel * imageDimensions.height}px`;
}
Expand Down Expand Up @@ -93,14 +97,50 @@ function renderScan(selected, scan, index) {
}


function SawmillViewerFilter({ scanData, selected, imageDimensions, settings }) {
function SawmillViewerFilter({ scanData, selected, settings }) {
const [imageDimensions, setImageDimensions] = useState(null);
const listRef = useRef();
const total = scanData.length;

function handleImageLoad(e) {
const dimensions = {
width: e.target.naturalWidth,
height: e.target.naturalHeight
};

if (dimensions.width > 0 && dimensions.height > 0) {
setImageDimensions(dimensions);
}
}

useLayoutEffect(() => {
if (total > 0 && listRef.current !== null) {
const image = listRef.current.lastElementChild.querySelector("img");
if (image !== null) {
if (image.complete) {
// Already loaded, so set image size immediately
handleImageLoad({ target: image });
} else {
// Wait for loading to complete before setting dimensions
image.addEventListener("load", handleImageLoad, { once: true });

return () => {
image.removeEventListener("load", handleImageLoad);
};
}
}
}

return undefined;
}, [scanData, total]);

if (total === 0) {
return html`<${SawmillAboutBox} />`;
}

const filterProps = {
class: getFilterClasses(settings),
ref: listRef,
class: getFilterClasses(imageDimensions, settings),
style: getFilterStyles(imageDimensions, scanData, settings)
};

Expand Down
6 changes: 2 additions & 4 deletions _site/load_jpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ async function loadJPEG(files, callback) {

console.log(`Inspecting ${file.name}`);
const rawBuffer = [address, bufferLength];
let nextOffset = inspector._getStartOfFrameOffset(...rawBuffer);
const imageWidth = inspector._getImageWidth(nextOffset, ...rawBuffer);
const imageHeight = inspector._getImageHeight(nextOffset, ...rawBuffer);
let nextOffset = 0;

const scanEndOffsets = [];
for (;;) {
Expand All @@ -70,7 +68,7 @@ async function loadJPEG(files, callback) {
console.log(`Scan end offsets for ${file.name}:`);
console.log(scanEndOffsets);

callback({ uint8Array, imageWidth, imageHeight, scanEndOffsets });
callback({ uint8Array, scanEndOffsets });

// Free the raw buffer and release the WASM instance
// TODO: Cache the compiled WASM and reuse
Expand Down
131 changes: 0 additions & 131 deletions src/jpeg_inspector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,6 @@ static const uint16_t MARKER_EOI = 0xFFD9;
// Scan start marker
static const uint16_t MARKER_SOS = 0xFFDA;

// Start of Frame marker containing image dimensions
static const uint16_t MARKER_SOF0 = 0xFFC0;
static const uint16_t MASK_SOF = 0xFFF0;

// Markers to ignore when matching Start of Frame
static const uint16_t MARKER_DHT = 0xFFC4;
static const uint16_t MARKER_JPG = 0xFFC8;
static const uint16_t MARKER_DAC = 0xFFCC;

// Zero-stuffed "marker" used to escape real 0xFF values in the coded scan data
static const uint16_t MARKER_IGNORE = 0xFF00;

Expand All @@ -74,112 +65,17 @@ static const uint16_t MASK_RESTART_MODULUS = 0x0007;
// Prototypes
static bool areInputsValid(int32_t startOffset, const uint8_t* buffer, int32_t length);
static bool isStartOfImagePresent(const uint8_t* buffer, int32_t length);
static bool isStartOfFramePresent(int32_t startOfFrameOffset, const uint8_t* buffer, int32_t length);
static bool canGetByte(int32_t offset, int32_t length);
static uint8_t getByte(int32_t offset, const uint8_t* buffer, int32_t length);
static bool canGetWord(int32_t offset, int32_t length);
static uint16_t getWord(int32_t offset, const uint8_t* buffer, int32_t length);
static int32_t findNextMarker(int32_t offset, const uint8_t* buffer, int32_t length);
static int32_t skipMarkerSegment(int32_t offset, const uint8_t* buffer, int32_t length);
static bool isStartOfFrameMarker(uint16_t marker);
static bool isRestartMarker(uint16_t marker);


extern "C" {

int32_t EMSCRIPTEN_KEEPALIVE getStartOfFrameOffset(const uint8_t* buffer, int32_t length)
{
// Start immediately after the SOI marker
int32_t offset = 2;

if (!areInputsValid(offset, buffer, length) || !isStartOfImagePresent(buffer, length))
{
// If all else fails, skip to the end of the buffer
return length;
}

// Find Start of Frame segment
while(offset < length)
{
offset = findNextMarker(offset, buffer, length);
if (!canGetWord(offset, length))
{
offset = length;
break;
}

uint16_t marker = getWord(offset, buffer, length);
if (isStartOfFrameMarker(marker))
{
DEBUG_LOG("Start of Frame @ %d", offset);

break;
}

offset += 2;
offset = skipMarkerSegment(offset, buffer, length);

DEBUG_LOG(" Skipping non-SOF marker %04x @ %d", marker, offset);
}

// Return end offset clamped to length
return std::min(offset, length);
}

uint16_t EMSCRIPTEN_KEEPALIVE getImageWidth(int32_t startOfFrameOffset, const uint8_t* buffer, int32_t length)
{
if (!areInputsValid(startOfFrameOffset, buffer, length) ||
!isStartOfImagePresent(buffer, length) ||
!isStartOfFramePresent(startOfFrameOffset, buffer, length))
{
// If all else fails, say the image width is 0
return 0;
}

// Check the Start of Frame segment is well-formed
const int32_t widthOffset = 5;
int32_t offset = startOfFrameOffset + 2;
if (!canGetWord(offset, length) || getWord(offset, buffer, length) < 2 + widthOffset)
{
return 0;
}

offset += widthOffset;
if (!canGetWord(offset, length))
{
return 0;
}

return getWord(offset, buffer, length);
}

uint16_t EMSCRIPTEN_KEEPALIVE getImageHeight(int32_t startOfFrameOffset, const uint8_t* buffer, int32_t length)
{
if (!areInputsValid(startOfFrameOffset, buffer, length) ||
!isStartOfImagePresent(buffer, length) ||
!isStartOfFramePresent(startOfFrameOffset, buffer, length))
{
// If all else fails, say the image height is 0
return 0;
}

// Check the Start of Frame segment is well-formed
const int32_t heightOffset = 3;
int32_t offset = startOfFrameOffset + 2;
if (!canGetWord(offset, length) || getWord(offset, buffer, length) < 2 + heightOffset)
{
return 0;
}

offset += heightOffset;
if (!canGetWord(offset, length))
{
return 0;
}

return getWord(offset, buffer, length);
}

int32_t EMSCRIPTEN_KEEPALIVE getScanEndOffset(int32_t startOffset, const uint8_t* buffer, int32_t length)
{
if (!areInputsValid(startOffset, buffer, length) || !isStartOfImagePresent(buffer, length))
Expand Down Expand Up @@ -284,27 +180,6 @@ static bool isStartOfImagePresent(const uint8_t* buffer, int32_t length)
return true;
}

static bool isStartOfFramePresent(int32_t startOfFrameOffset, const uint8_t* buffer, int32_t length)
{
// Check for the JPEG SOF marker
if (!canGetWord(startOfFrameOffset, length) ||
!isStartOfFrameMarker(getWord(startOfFrameOffset, buffer, length)))
{
#ifdef EMSCRIPTEN_DEBUG
DEBUG_LOG("Invalid image: No SOF marker");
DEBUG_LOG(" canGetWord(%d, %d) = %d", startOfFrameOffset, length, canGetWord(startOfFrameOffset, length));

if (canGetWord(0, length))
{
DEBUG_LOG(" getWord(%d, %p, %d) = %04x", startOfFrameOffset, buffer, length, getWord(startOfFrameOffset, buffer, length));
}
#endif // EMSCRIPTEN_DEBUG

return false;
}

return true;
}

static bool canGetByte(int32_t offset, int32_t length)
{
Expand Down Expand Up @@ -354,12 +229,6 @@ static int32_t skipMarkerSegment(int32_t offset, const uint8_t* buffer, int32_t
return offset + getWord(offset, buffer, length);
}

static bool isStartOfFrameMarker(uint16_t marker)
{
const bool isInSOFMarkerRange = ((marker & MASK_SOF) == MARKER_SOF0);
return isInSOFMarkerRange && !(marker == MARKER_DHT || marker == MARKER_JPG || marker == MARKER_DAC);
}

static bool isRestartMarker(uint16_t marker)
{
return (marker & ~MASK_RESTART_MODULUS) == MARKER_RESTART0;
Expand Down
4 changes: 0 additions & 4 deletions src/jpeg_inspector.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@
extern "C" {
#endif // __cplusplus

extern int32_t getStartOfFrameOffset(const uint8_t* buffer, int32_t length);
extern uint16_t getImageWidth(int32_t startOfFrameOffset, const uint8_t* buffer, int32_t length);
extern uint16_t getImageHeight(int32_t startOfFrameOffset, const uint8_t* buffer, int32_t length);

extern int32_t getScanEndOffset(int32_t startOffset, const uint8_t* buffer, int32_t length);

#ifdef __cplusplus
Expand Down
Loading

0 comments on commit 079cc39

Please sign in to comment.