Commit 4b2550db for libheif
commit 4b2550db960d97d1fbf5ce874a9c7eb0dbddc690
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sun May 17 20:54:11 2026 +0200
mini: fix orientation handling
diff --git a/libheif/mini.cc b/libheif/mini.cc
index 4f1af79e..43b61e4f 100644
--- a/libheif/mini.cc
+++ b/libheif/mini.cc
@@ -1475,34 +1475,6 @@ Error Box_mini::create_expanded_boxes(class HeifFile* file)
// TODO: replace this placeholder with pixi box version 1 once that is supported
ipco_box->append_child_box(std::make_shared<Box_free>()); // placeholder for entry 8
- if (get_orientation() == 2) {
- std::shared_ptr<Box_irot> irot = std::make_shared<Box_irot>();
- irot->set_rotation_ccw(2 * 90);
- ipco_box->append_child_box(irot); // entry 9
- } else if ((get_orientation() == 4) || (get_orientation() == 6) || (get_orientation() == 7)) {
- std::shared_ptr<Box_irot> irot = std::make_shared<Box_irot>();
- irot->set_rotation_ccw(1 * 90);
- ipco_box->append_child_box(irot); // entry 9
- } else if (get_orientation() == 5) {
- std::shared_ptr<Box_irot> irot = std::make_shared<Box_irot>();
- irot->set_rotation_ccw(3 * 90);
- ipco_box->append_child_box(irot); // entry 9
- } else {
- ipco_box->append_child_box(std::make_shared<Box_free>()); // placeholder for entry 9
- }
-
- if ((get_orientation() == 1) || (get_orientation() == 6)) {
- std::shared_ptr<Box_imir> imir = std::make_shared<Box_imir>();
- imir->set_mirror_direction(heif_transform_mirror_direction_horizontal);
- ipco_box->append_child_box(imir); // entry 10
- } else if ((get_orientation() == 3) || (get_orientation() == 4)) {
- std::shared_ptr<Box_imir> imir = std::make_shared<Box_imir>();
- imir->set_mirror_direction(heif_transform_mirror_direction_vertical);
- ipco_box->append_child_box(imir); // entry 10
- } else {
- ipco_box->append_child_box(std::make_shared<Box_free>()); // placeholder for entry 10
- }
-
auto ipma_box = std::make_shared<Box_ipma>();
file->set_ipma_box(ipma_box);
ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(1)});
@@ -1510,19 +1482,24 @@ Error Box_mini::create_expanded_boxes(class HeifFile* file)
ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, uint16_t(3)});
ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(4)});
ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(5)});
- ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(9)});
- ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(10)});
if (get_alpha_item_data_size() != 0) {
ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(6)});
ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{false, uint16_t(2)});
ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(7)});
ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{false, uint16_t(8)});
- ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(9)});
- ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(10)});
}
// TODO: will need more once we support HDR / gainmap representation
+ // Append irot/imir (only as needed) and associate them with the items.
+ // mini's orientation field uses standard EXIF orientation values 1..8,
+ // matching the heif_orientation enum.
+ auto orientation = static_cast<heif_orientation>(get_orientation());
+ file->add_orientation_properties(1, orientation);
+ if (get_alpha_item_data_size() != 0) {
+ file->add_orientation_properties(2, orientation);
+ }
+
auto iloc_box = std::make_shared<Box_iloc>();
file->set_iloc_box(iloc_box);
Box_iloc::Item main_item;
@@ -1590,49 +1567,34 @@ Error Box_mini::create_expanded_boxes(class HeifFile* file)
}
-// --- Reverse mapping: irot/imir to EXIF orientation ---
+// --- Map a single irot/imir box to its equivalent heif_orientation ---
+//
+// Used together with heif_orientation_concat() to compose the cumulative
+// orientation of a sequence of transform boxes. Box order in ipma is not
+// strictly fixed across files, so accumulating via concat lets us recover
+// the right orientation regardless of whether irot or imir appears first.
-static uint8_t compute_orientation_from_transforms(const Box_irot* irot, const Box_imir* imir)
+static heif_orientation transform_box_to_orientation(const std::shared_ptr<Box>& box)
{
- int rotation = irot ? irot->get_rotation_ccw() : 0;
- bool has_mirror = (imir != nullptr);
- heif_transform_mirror_direction mirror_dir = has_mirror ? imir->get_mirror_direction() : heif_transform_mirror_direction_horizontal;
-
- // Reverse of create_expanded_boxes (mini.cc lines 955-981):
- // orientation 1: no transform
- // orientation 2: irot(180) only
- // orientation 3: imir(horizontal) only
- // orientation 4: imir(vertical) only
- // orientation 5: irot(270) only
- // orientation 6: irot(90) + imir(horizontal)
- // orientation 7: irot(90) only
- // orientation 8: irot(90) + imir(vertical)
-
- if (!has_mirror) {
- switch (rotation) {
- case 0: return 1;
- case 180: return 2;
- case 270: return 5;
- case 90: return 7;
- default: return 1;
+ if (auto irot = std::dynamic_pointer_cast<Box_irot>(box)) {
+ // irot stores the rotation in counter-clockwise degrees.
+ switch (irot->get_rotation_ccw()) {
+ case 0: return heif_orientation_normal;
+ case 90: return heif_orientation_rotate_270_cw; // 90 ccw == 270 cw
+ case 180: return heif_orientation_rotate_180;
+ case 270: return heif_orientation_rotate_90_cw; // 270 ccw == 90 cw
+ default: return heif_orientation_normal;
}
}
- else {
- if (mirror_dir == heif_transform_mirror_direction_horizontal) {
- switch (rotation) {
- case 0: return 3;
- case 90: return 6;
- default: return 1;
- }
- }
- else { // vertical
- switch (rotation) {
- case 0: return 4;
- case 90: return 8;
- default: return 1;
- }
+ if (auto imir = std::dynamic_pointer_cast<Box_imir>(box)) {
+ switch (imir->get_mirror_direction()) {
+ case heif_transform_mirror_direction_horizontal:
+ return heif_orientation_flip_horizontally;
+ case heif_transform_mirror_direction_vertical:
+ return heif_orientation_flip_vertically;
}
}
+ return heif_orientation_normal;
}
@@ -1798,10 +1760,13 @@ std::shared_ptr<Box_mini> Box_mini::create_from_heif_file(HeifFile* file)
std::shared_ptr<Box_pixi> pixi;
std::shared_ptr<Box_colr> colr_nclx;
std::shared_ptr<Box_colr> colr_icc;
- std::shared_ptr<Box_irot> irot;
- std::shared_ptr<Box_imir> imir;
std::shared_ptr<Box> codec_config;
+ // Accumulate cumulative orientation by composing transforms in property
+ // order. Use heif_orientation_concat() so the result is correct regardless
+ // of whether irot or imir comes first in ipma.
+ heif_orientation orientation = heif_orientation_normal;
+
for (auto& prop : properties) {
if (auto p = std::dynamic_pointer_cast<Box_ispe>(prop)) {
ispe = p;
@@ -1817,11 +1782,9 @@ std::shared_ptr<Box_mini> Box_mini::create_from_heif_file(HeifFile* file)
colr_icc = p;
}
}
- else if (auto p = std::dynamic_pointer_cast<Box_irot>(prop)) {
- irot = p;
- }
- else if (auto p = std::dynamic_pointer_cast<Box_imir>(prop)) {
- imir = p;
+ else if (std::dynamic_pointer_cast<Box_irot>(prop) ||
+ std::dynamic_pointer_cast<Box_imir>(prop)) {
+ orientation = heif_orientation_concat(orientation, transform_box_to_orientation(prop));
}
else if (std::dynamic_pointer_cast<Box_av1C>(prop) || std::dynamic_pointer_cast<Box_hvcC>(prop)) {
codec_config = prop;
@@ -1920,9 +1883,8 @@ std::shared_ptr<Box_mini> Box_mini::create_from_heif_file(HeifFile* file)
}
mini->set_explicit_cicp_flag(need_explicit_cicp);
- // Orientation
- uint8_t orientation = compute_orientation_from_transforms(irot.get(), imir.get());
- mini->set_orientation(orientation);
+ // Orientation (accumulated via heif_orientation_concat during the property walk above)
+ mini->set_orientation(static_cast<uint8_t>(orientation));
// Codec config
auto config_bytes = extract_codec_config_bytes(codec_config);