Skip to content

🔧 fix(TiffPreviewParser): rejeter le JPEG lossless des donnĂ©es du capteur#24

Merged
ronan-develop merged 2 commits into
mainfrom
fix/#23-cr2-jpeg-lossless
Jul 16, 2026
Merged

🔧 fix(TiffPreviewParser): rejeter le JPEG lossless des donnĂ©es du capteur#24
ronan-develop merged 2 commits into
mainfrom
fix/#23-cr2-jpeg-lossless

Conversation

@ronan-develop

Copy link
Copy Markdown
Owner

Closes #23

Sur un Canon 5D Mark IV, extract() rendait 28,8 Mo indécodables au lieu de la preview de 2 Mo. Pas une exception : une réponse fausse qui passait tous les contrÎles.

La cause

Canon stocke les donnĂ©es du capteur d'un CR2 en JPEG lossless, dans des IFD qui dĂ©clarent honnĂȘtement Compression = 6 :

IFD Dimensions Compression Taille Nature
IFD0 6720×4480 6 2 096 284 o ✅ la vraie preview
IFD3 6880×4544 6 30 199 466 o ❌ capteur (lossless)
IFD4 6880×4544 6 28 841 907 o ❌ capteur (lossless)

Le filtre les acceptait, et comme ils sont les plus gros, « garder la plus grande preview » les choisissait.

La solution : le marqueur SOF

Compression = 6 ne distingue pas une preview des donnĂ©es brutes. Le SOF si — vĂ©rifiĂ© sur les six appareils :

canon-5d-mark-iv    SOF3 LOSSLESS   ← le seul
canon-eos-r         SOF0 baseline
canon-eos-rp        SOF0 baseline
iphone-12-pro       SOF0 baseline
nikon-d750          SOF0 baseline
sony-a7             SOF0 baseline

Les candidats sont dĂ©sormais validĂ©s avant d'ĂȘtre retenus : le plus gros bloc gagne seulement s'il est rĂ©ellement dĂ©codable (SOF0, SOF1, SOF2).

đŸȘ€ Le piĂšge du diagnostic

getimagesizefromstring() acceptait ce JPEG lossless — elle ne lit que l'en-tĂȘte et annonçait fiĂšrement 3440×2272. Seul imagecreatefromstring() rĂ©vĂ©lait l'Ă©chec.

Ma validation croisĂ©e n'Ă©tait donc pas assez stricte : elle vĂ©rifiait qu'un en-tĂȘte Ă©tait bien formĂ©, pas qu'une image existait. Les six previews sont maintenant contrĂŽlĂ©es Ă  l'ouverture rĂ©elle.

Sémantique

Un bloc sans SOF exploitable donne PreviewNotFoundException, plus CorruptedFileException : le fichier n'est pas corrompu, c'est le candidat qui n'est pas une preview.

Résultat

Avant AprĂšs
CR2 5D Mark IV 28,8 Mo indĂ©codables ❌ 6720×4480, 2047 Ko ✅

Preview ouverte et vĂ©rifiĂ©e Ă  l'Ɠil : une charte SpyderCHECKR en montagne, couleurs justes.

OK (135 tests, 220 assertions)


teur

Sur un Canon 5D Mark IV, extract() rendait 28,8 Mo au lieu de la preview de
2 Mo — et ces octets n'Ă©taient pas affichables. Le pire cas : pas une
exception, une réponse fausse qui passait tous les contrÎles.

Canon stocke les données du capteur d'un CR2 en JPEG LOSSLESS, dans des IFD qui
dĂ©clarent honnĂȘtement Compression = 6. Le filtre les acceptait donc, et comme
ces blocs sont les plus gros du fichier, la stratégie « garder la plus grande
preview » les choisissait :

  IFD0  6720x4480  Compression=6   2 096 284 o  ← la vraie preview
  IFD3  6880x4544  Compression=6  30 199 466 o  ← capteur, JPEG lossless
  IFD4  6880x4544  Compression=6  28 841 907 o  ← capteur, JPEG lossless

Compression = 6 ne distingue donc pas une preview des données brutes. Le
marqueur SOF, si : vérifié sur six appareils, toutes les previews utilisent
SOF0 baseline ; seul le capteur d'un CR2 utilise SOF3 lossless.

Les candidats sont dĂ©sormais validĂ©s avant d'ĂȘtre retenus : le plus gros bloc
gagne seulement s'il est réellement décodable (SOF0, SOF1 ou SOF2).

Un piĂšge dans le diagnostic : getimagesizefromstring() acceptait ce JPEG
lossless — elle ne lit que l'en-tĂȘte et annonçait 3440x2272. Seul
imagecreatefromstring() révélait l'échec. La validation croisée par GD n'était
pas assez stricte.

Un bloc sans SOF exploitable donne désormais PreviewNotFoundException et non
CorruptedFileException : le fichier n'est pas corrompu, c'est le candidat qui
n'est pas une preview.

Vérifié : les 6 previews des fichiers réels s'ouvrent par imagecreatefromstring().
CR2 5D Mark IV → 6720x4480, 2047 Ko.

Closes #23
Six appareils vérifiés (raw.pixls.us, CC0) : Canon 5D Mark IV, EOS R, EOS RP,
Nikon D750, Sony α7 et iPhone 12 Pro en ProRAW. Les cinq formats du périmÚtre
sont couverts.

Chaque preview est ouverte par imagecreatefromstring(), pas seulement décodée
en en-tĂȘte : c'est cette distinction qui a rĂ©vĂ©lĂ© le bug du JPEG lossless.

Le statut passe de « waiting on validation » à « validated ». 1.0.0 reste non
taggé : l'API est stable mais un release est un engagement semver, et d'autres
appareils mĂ©ritent d'ĂȘtre testĂ©s d'abord.
@ronan-develop
ronan-develop merged commit 6bee763 into main Jul 16, 2026
6 checks passed
@ronan-develop
ronan-develop deleted the fix/#23-cr2-jpeg-lossless branch July 16, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🔧 fix(TiffPreviewParser): le CR2 rend les donnĂ©es du capteur au lieu de la preview

1 participant