Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ is the same as `Framebuffer.Fit()` -- it performs a cropping resize that does no

* `NormalizeOrientation`: If `true`, `Transform()` will inspect the image orientation and
normalize the output so that it is facing in the standard orientation. This will undo
JPEG EXIF-based orientation.
JPEG and AVIF metadata-based orientation transforms.

* `EncodeOptions`: Of type `map[int]int`, same options accepted as [Encoder.Encode()](#encoder). This
controls output encode quality.
Expand Down Expand Up @@ -154,7 +154,7 @@ func (h lilliput.ImageHeader) Orientation() lilliput.ImageOrientation
```
Returns the metadata-based orientation of the image. This function can
be called on all image types but presently only detects orientation in
JPEG images. An orientation value of 1 indicates default orientation.
JPEG and AVIF images. An orientation value of 1 indicates default orientation.
All other values indicate some kind of rotation or mirroring.

### PixelType
Expand Down
60 changes: 60 additions & 0 deletions avif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,66 @@ int avif_decoder_get_pixel_type(const avif_decoder d)
return d->has_alpha ? CV_8UC4 : CV_8UC3;
}

int avif_decoder_get_orientation(const avif_decoder d)
{
if (!d || !d->decoder || !d->decoder->image) {
return CV_IMAGE_ORIENTATION_TL;
}

const avifImage* image = d->decoder->image;
const bool has_irot = (image->transformFlags & AVIF_TRANSFORM_IROT) != 0;
const bool has_imir = (image->transformFlags & AVIF_TRANSFORM_IMIR) != 0;

// Map AVIF irot (rotation) and imir (mirror) to EXIF/OpenCV orientation
// irot.angle: 0=0 degrees, 1=90 degrees CCW, 2=180 degrees, 3=270 degrees CCW
// imir.axis: 0=vertical flip (top/bottom), 1=horizontal flip (left/right)

constexpr uint8_t NOT_MIRRORED = 0xFF;
uint8_t rotation = has_irot ? image->irot.angle : 0;
uint8_t mirror_axis = has_imir ? image->imir.axis : NOT_MIRRORED;

// If the rotation is invalid, reset it to 0
rotation = rotation <= 3 ? rotation : 0;
// If the mirroring type is invalid, ignore it
mirror_axis = mirror_axis <= 1 ? mirror_axis : NOT_MIRRORED;

// Since rotations and mirroring are non-commutative, e.g.:
// [rotate 90 degrees counter-clockwise] then [mirror horizontally]
// = [mirror horizontally] then [rotate 90 degrees clockwise],
// it is important to note that mirroring is applied before rotation.
// Corresponding EXIF orientation values are shown in comments.
if (mirror_axis == NOT_MIRRORED) {
// No mirroring, only rotation
switch (rotation) {
case 0: return CV_IMAGE_ORIENTATION_TL; // 1: Normal
case 1: return CV_IMAGE_ORIENTATION_LB; // 8: 90 degrees CCW
case 2: return CV_IMAGE_ORIENTATION_BR; // 3: 180 degrees
case 3: return CV_IMAGE_ORIENTATION_RT; // 6: 270 degrees CCW (90 degrees CW)
default: return CV_IMAGE_ORIENTATION_TL;
}
} else if (mirror_axis == 0) {
// Vertical flip (top/bottom exchange)
switch (rotation) {
case 0: return CV_IMAGE_ORIENTATION_BL; // 4: Mirrored vertical
case 1: return CV_IMAGE_ORIENTATION_LT; // 5: Mirrored vertical + 90 degrees CCW
case 2: return CV_IMAGE_ORIENTATION_TR; // 2: Mirrored vertical + 180 degrees
case 3: return CV_IMAGE_ORIENTATION_RB; // 7: Mirrored vertical + 270 degrees CCW
default: return CV_IMAGE_ORIENTATION_BL;
}
} else if (mirror_axis == 1) {
// Horizontal flip (left/right exchange)
switch (rotation) {
case 0: return CV_IMAGE_ORIENTATION_TR; // 2: Mirrored horizontal
case 1: return CV_IMAGE_ORIENTATION_RB; // 7: Mirrored horizontal + 90 degrees CCW
case 2: return CV_IMAGE_ORIENTATION_BL; // 4: Mirrored horizontal + 180 degrees
case 3: return CV_IMAGE_ORIENTATION_LT; // 5: Mirrored horizontal + 270 degrees CCW
default: return CV_IMAGE_ORIENTATION_TR;
}
}

return CV_IMAGE_ORIENTATION_TL;
}

bool avif_decoder_is_animated(const avif_decoder d)
{
if (!d || !d->decoder) {
Expand Down
2 changes: 1 addition & 1 deletion avif.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (d *avifDecoder) Header() (*ImageHeader, error) {
width: int(C.avif_decoder_get_width(d.decoder)),
height: int(C.avif_decoder_get_height(d.decoder)),
pixelType: PixelType(C.avif_decoder_get_pixel_type(d.decoder)),
orientation: OrientationTopLeft,
orientation: ImageOrientation(C.avif_decoder_get_orientation(d.decoder)),
numFrames: int(C.avif_decoder_get_num_frames(d.decoder)),
contentLength: len(d.buf),
}, nil
Expand Down
1 change: 1 addition & 0 deletions avif.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void avif_decoder_release(avif_decoder d);
int avif_decoder_get_width(const avif_decoder d);
int avif_decoder_get_height(const avif_decoder d);
int avif_decoder_get_pixel_type(const avif_decoder d);
int avif_decoder_get_orientation(const avif_decoder d);
int avif_decoder_get_num_frames(const avif_decoder d);
uint32_t avif_decoder_get_duration(const avif_decoder d);
uint32_t avif_decoder_get_loop_count(const avif_decoder d);
Expand Down