🔧 fix(ExtractedPreview): exposer l'orientation EXIF#34
Merged
Conversation
Les 8 valeurs du tag 0x0112 mélangent rotations et miroirs : les traiter comme de simples entiers oblige chaque utilisateur à réapprendre la table, et confondre un miroir avec une rotation produit une image inversée. L'enum porte ce que l'appelant veut vraiment savoir : degrees() pour imagerotate() ou une transform CSS, isMirrored() pour les 4 valeurs qui n'en sont pas, isUpright() pour écarter le cas courant d'un seul test, et swapsDimensions() parce qu'un quart de tour échange largeur et hauteur. fromExif() tolère l'inattendu : un tag à 0, 9 ou absent ne doit pas faire échouer une extraction par ailleurs réussie — on suppose l'image droite. 27 tests couvrant les 8 valeurs sur les 4 méthodes.
Un CR3 range des TIFF complets dans ses boîtes CMT1/CMT2 — c'est là que vit son orientation. TiffReader ne savait lire qu'un fichier entier ; il aurait fallu extraire CMT1 dans un fichier temporaire, ce qui est hors de question dans src/. fromRange($path, $offset, $length) fait travailler le reader sur une fenêtre : ni écriture disque, ni copie mémoire. Les offsets restent relatifs au début du TIFF embarqué — une base interne les traduit vers le fichier hôte, et ne fuit jamais hors de la classe. La fenêtre borne aussi les lectures : un offset qui en sort est hors bornes, même s'il pointe dans le fichier hôte. Le fichier entier n'est plus qu'un cas particulier (base = 0), donc les 138 tests existants passent inchangés. Cette pièce resservira pour les EXIF du CR3 (#33).
…t sortaient couchées Une photo prise en portrait sortait couchée à 90°. Vérifié à l'œil sur un DNG d'iPhone 12 Pro : Orientation = 6, et la preview extraite affichait le ciel à gauche. Le bug était dans nos fixtures depuis le début — 138 tests verts ne l'ont jamais vu, il a fallu ouvrir l'image. Ça touche tout smartphone tenu verticalement, donc une part importante du contenu réel d'une galerie. On ne peut pas pivoter l'image nous-mêmes : ça exigerait GD, que ce package existe précisément pour éviter. On rend l'information, l'appelant décide — un transform: rotate(90deg) en CSS ne coûte rien, et le PHPDoc explique comment faire avec imagerotate() pour qui a GD. Lecture par format : - TIFF : tag 0x0112 de l'IFD0, qui vaut pour toute la prise de vue — l'appareil enregistre une fois comment il était tenu, pas par image ; - CR3 : pas d'IFD TIFF direct, l'orientation vit dans la boîte CMT1 qui EST un TIFF complet. TiffReader::fromRange() la lit en place. Une propriété AVEC valeur par défaut : new ExtractedPreview($jpeg, 100, 50, Format::CR2) compile toujours. L'API grandit sans rien casser — c'est une mineure, pas une majeure. Et le choix compte : Composer bloque les majeures par défaut, une 2.0.0 aurait obligé les utilisateurs à éditer leur composer.json pour un correctif dont ils ont besoin. Vérifié de bout en bout : l'iPhone rend Rotate90/90°, la rotation appliquée redresse la photo (4032x3024 → 3024x4032, comme swapsDimensions() l'annonce), et les 8 autres fixtures rendent Normal. Closes #32
Le README montre les deux façons de redresser : un transform CSS, gratuit et sans dépendance, ou imagerotate() pour qui a GD — en signalant qu'il tourne dans l'autre sens. docs/index.md détaille l'enum et surtout d'où l'orientation est lue selon le format : le tag 0x0112 de l'IFD0 en TIFF, la boîte CMT1 en CR3 — qui est un TIFF complet, lu en place par TiffReader::fromRange().
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #32
Le bug, vu à l'œil
Une photo prise en portrait sortait couchée à 90°. La fixture iPhone 12 Pro a
Orientation = 6, et la preview extraite affichait le ciel à gauche et l'herbe à droite.Le bug était dans nos fixtures depuis le début. 138 tests verts ne l'ont jamais vu — l'extraction fonctionnait, elle rendait juste une image tournée. Il a fallu ouvrir l'image.
Ça touche tout smartphone tenu verticalement, donc une part importante du contenu réel d'une galerie.
On ne pivote pas, on informe
Faire tourner le JPEG demanderait GD — la dépendance que ce package existe pour éviter. On rend l'information, l'appelant décide :
Un enum plutôt qu'un
int: les 8 valeurs mélangent rotations et miroirs, et confondre les deux produit une image inversée.Lecture par format
0x0112de l'IFD0 — il vaut pour toute la prise de vueCMT1, qui est un TIFF completLe CR3 imposait une pièce nouvelle :
TiffReader::fromRange($path, $offset, $length).CMT1est un TIFF au milieu du fichier ; sans ça il aurait fallu l'extraire dans un fichier temporaire, ce qui est hors de question danssrc/. Le reader travaille désormais sur une fenêtre — ni écriture disque, ni copie mémoire. Le fichier entier n'est plus qu'un cas particulier (base = 0), donc les 138 tests existants passent inchangés.Cette pièce resservira pour les EXIF du CR3 (#33).
1.1.0, pas 2.0.0
La propriété a une valeur par défaut :
new ExtractedPreview($jpeg, 100, 50, Format::CR2)compile toujours. L'API grandit sans rien casser — c'est une mineure.Le choix compte : Composer bloque les majeures par défaut (
^1.0ne prend pas2.0). Une2.0.0aurait obligé les utilisateurs à éditer leurcomposer.jsonpour un correctif dont ils ont besoin.Vérifié de bout en bout
Rotation appliquée : 4032×3024 → 3024×4032, exactement ce que
swapsDimensions()annonçait — et la photo est droite, ciel en haut.