Skip to content

Commit 9b6cdd5

Browse files
authored
tiff-orientation
Debug tool for figuring out if a JPEG has TIFF orientation data in it. Claude prompts: https://gist.github.com/simonw/9bf1bd4cce6d113c55db0e5a03769b52
1 parent 469aeb1 commit 9b6cdd5

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed

tiff-orientation.html

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>TIFF Orientation Reader</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
display: flex;
11+
justify-content: center;
12+
align-items: center;
13+
min-height: 100vh;
14+
margin: 0;
15+
background-color: #f0f0f0;
16+
}
17+
.container {
18+
text-align: center;
19+
background-color: white;
20+
padding: 2rem;
21+
border-radius: 8px;
22+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
23+
max-width: 800px;
24+
width: 100%;
25+
}
26+
#drop-zone {
27+
border: 2px dashed #ccc;
28+
border-radius: 20px;
29+
width: 100%;
30+
padding: 20px;
31+
text-align: center;
32+
cursor: pointer;
33+
box-sizing: border-box;
34+
}
35+
#drop-zone.dragover {
36+
background-color: #e1e1e1;
37+
}
38+
#result, #debug {
39+
margin-top: 20px;
40+
text-align: left;
41+
white-space: pre-wrap;
42+
word-break: break-all;
43+
}
44+
</style>
45+
</head>
46+
<body>
47+
<div class="container">
48+
<div id="drop-zone">
49+
<p>Drag & Drop a JPEG image here or click to select</p>
50+
<input type="file" id="file-input" accept="image/jpeg" style="display: none;">
51+
</div>
52+
<div id="result"></div>
53+
<div id="debug"></div>
54+
</div>
55+
56+
<script>
57+
const dropZone = document.getElementById('drop-zone');
58+
const fileInput = document.getElementById('file-input');
59+
const result = document.getElementById('result');
60+
const debug = document.getElementById('debug');
61+
62+
dropZone.addEventListener('click', () => fileInput.click());
63+
dropZone.addEventListener('dragover', (e) => {
64+
e.preventDefault();
65+
dropZone.classList.add('dragover');
66+
});
67+
dropZone.addEventListener('dragleave', () => {
68+
dropZone.classList.remove('dragover');
69+
});
70+
dropZone.addEventListener('drop', handleDrop);
71+
fileInput.addEventListener('change', handleFileSelect);
72+
73+
function handleDrop(e) {
74+
e.preventDefault();
75+
dropZone.classList.remove('dragover');
76+
const file = e.dataTransfer.files[0];
77+
processFile(file);
78+
}
79+
80+
function handleFileSelect(e) {
81+
const file = e.target.files[0];
82+
processFile(file);
83+
}
84+
85+
function processFile(file) {
86+
if (file && file.type === 'image/jpeg') {
87+
const reader = new FileReader();
88+
reader.onload = (e) => {
89+
const view = new DataView(e.target.result);
90+
try {
91+
const orientation = readTiffOrientation(view);
92+
result.textContent = `TIFF Orientation: ${getOrientationInfo(orientation)}`;
93+
debug.textContent = `Orientation value: ${orientation}`;
94+
} catch (err) {
95+
result.textContent = `Error: ${err.message}`;
96+
debug.textContent = err.debugInfo || '';
97+
}
98+
};
99+
reader.readAsArrayBuffer(file);
100+
} else {
101+
result.textContent = 'Please select a JPEG image';
102+
debug.textContent = '';
103+
}
104+
}
105+
106+
function readTiffOrientation(view) {
107+
let debugInfo = '';
108+
const length = view.byteLength;
109+
debugInfo += `File size: ${length} bytes\n`;
110+
111+
// Look for EXIF header
112+
const exifStart = findExifStart(view);
113+
if (exifStart === -1) {
114+
throw Object.assign(new Error('EXIF data not found'), { debugInfo });
115+
}
116+
debugInfo += `EXIF start: ${exifStart}\n`;
117+
118+
const tiffStart = exifStart + 6; // Skip the "Exif\0\0" part
119+
debugInfo += `TIFF start: ${tiffStart}\n`;
120+
121+
// Determine endianness
122+
const endian = view.getUint16(tiffStart, false);
123+
const isLittleEndian = (endian === 0x4949); // 'II' in ASCII
124+
debugInfo += `Endianness: ${isLittleEndian ? 'Little Endian' : 'Big Endian'}\n`;
125+
126+
// Check TIFF header validity
127+
const tiffMagic = view.getUint16(tiffStart + 2, isLittleEndian);
128+
if (tiffMagic !== 42) {
129+
throw Object.assign(new Error('Not a valid TIFF header'), { debugInfo });
130+
}
131+
debugInfo += 'Valid TIFF header\n';
132+
133+
// Get offset to first IFD
134+
const ifdOffset = view.getUint32(tiffStart + 4, isLittleEndian);
135+
const ifdStart = tiffStart + ifdOffset;
136+
debugInfo += `IFD start: ${ifdStart}\n`;
137+
138+
// Number of directory entries
139+
const numEntries = view.getUint16(ifdStart, isLittleEndian);
140+
debugInfo += `Number of IFD entries: ${numEntries}\n`;
141+
142+
// Loop through IFD entries
143+
for (let i = 0; i < numEntries; i++) {
144+
const entryOffset = ifdStart + 2 + (i * 12); // Each entry is 12 bytes long
145+
const tag = view.getUint16(entryOffset, isLittleEndian);
146+
debugInfo += `Tag: 0x${tag.toString(16).toUpperCase()}\n`;
147+
148+
if (tag === 0x0112) { // Orientation tag
149+
const orientation = view.getUint16(entryOffset + 8, isLittleEndian);
150+
debugInfo += `Orientation found: ${orientation}\n`;
151+
Object.assign(readTiffOrientation, { debugInfo });
152+
return orientation;
153+
}
154+
}
155+
156+
throw Object.assign(new Error('Orientation tag not found in TIFF data'), { debugInfo });
157+
}
158+
159+
function findExifStart(view) {
160+
const target = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]; // "Exif\0\0"
161+
for (let i = 0; i < view.byteLength - 6; i++) {
162+
if (target.every((v, j) => view.getUint8(i + j) === v)) {
163+
return i;
164+
}
165+
}
166+
return -1;
167+
}
168+
169+
function getOrientationInfo(orientation) {
170+
const orientations = {
171+
1: "Normal",
172+
3: "Rotated 180°",
173+
6: "Rotated 90° CW",
174+
8: "Rotated 270° CW"
175+
};
176+
return orientations[orientation] || `Unknown (${orientation})`;
177+
}
178+
</script>
179+
</body>
180+
</html>

0 commit comments

Comments
 (0)