Commit 472cbfa7 for libheif
commit 472cbfa7a87a21a00db16ae04ed35fa722ebea1b
Author: Brad Hards <bradh@frogmouth.net>
Date: Sun Feb 22 19:37:41 2026 +1100
feat: add support for omnidirectional (OMAF) image projection
This is defined in ISO/IEC 23090-2.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8b74df99..77986f65 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,8 @@ include (TestBigEndian)
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
add_compile_definitions(IS_BIG_ENDIAN=${IS_BIG_ENDIAN})
+option(HEIF_WITH_OMAF "Enable omnidirectional media format (OMAF) support." ON)
+
# --- codec plugins
option(ENABLE_PLUGIN_LOADING "Support loading of plugins" ON)
@@ -530,6 +532,12 @@ else()
set(LIBS_PRIVATE "-lstdc++")
endif()
+if(HEIF_WITH_OMAF)
+ set(WITH_OMAF "1")
+else ()
+ set(WITH_OMAF "0")
+endif()
+
configure_file(libheif.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libheif.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libheif.pc
diff --git a/CMakePresets.json b/CMakePresets.json
index 7bd0d39e..22d05e1c 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -25,6 +25,7 @@
"WITH_DAV1D" : "ON",
"WITH_DAV1D_PLUGIN" : "OFF",
"ENABLE_EXPERIMENTAL_MINI_FORMAT" : "ON",
+ "HEIF_WITH_OMAF" : "ON",
"WITH_LIBDE265" : "ON",
"WITH_LIBDE265_PLUGIN" : "OFF",
"WITH_RAV1E" : "ON",
@@ -75,6 +76,7 @@
"BUILD_SHARED_LIBS": "ON",
"BUILD_TESTING" : "OFF",
"ENABLE_EXPERIMENTAL_FEATURES" : "OFF",
+ "HEIF_WITH_OMAF" : "ON",
"CMAKE_COMPILE_WARNING_AS_ERROR" : "OFF",
"ENABLE_PLUGIN_LOADING" : "ON",
@@ -131,6 +133,7 @@
"BUILD_SHARED_LIBS": "ON",
"BUILD_TESTING" : "OFF",
"ENABLE_EXPERIMENTAL_FEATURES" : "OFF",
+ "HEIF_WITH_OMAF" : "ON",
"CMAKE_COMPILE_WARNING_AS_ERROR" : "OFF",
"ENABLE_PLUGIN_LOADING" : "OFF",
diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc
index 99f04454..334b5895 100644
--- a/examples/heif_enc.cc
+++ b/examples/heif_enc.cc
@@ -126,6 +126,10 @@ int sequence_max_frames = 0; // 0 -> no maximum
std::string option_gimi_track_id;
std::string option_sai_data_file;
+#if HEIF_WITH_OMAF
+int option_image_projection;
+heif_image_projection image_projection = heif_image_projection::flat;
+#endif
enum heif_output_nclx_color_profile_preset
{
@@ -192,6 +196,9 @@ const int OPTION_METADATA_COMPRESSION = 1034;
const int OPTION_SEQUENCES_GIMI_TRACK_ID = 1035;
const int OPTION_SEQUENCES_SAI_DATA_FILE = 1036;
const int OPTION_USE_HEVC_COMPRESSION = 1037;
+#if HEIF_WITH_OMAF
+const int OPTION_SET_IMAGE_PROJECTION = 1038;
+#endif
static option long_options[] = {
{(char* const) "help", no_argument, 0, 'h'},
@@ -260,6 +267,9 @@ static option long_options[] = {
{(char* const) "max-keyframe-distance", required_argument, nullptr, OPTION_SEQUENCES_MAX_KEYFRAME_DISTANCE},
{(char* const) "set-gimi-track-id", required_argument, nullptr, OPTION_SEQUENCES_GIMI_TRACK_ID},
{(char* const) "sai-data-file", required_argument, nullptr, OPTION_SEQUENCES_SAI_DATA_FILE},
+#if HEIF_WITH_OMAF
+ {(char* const) "image-projection", required_argument, nullptr, OPTION_SET_IMAGE_PROJECTION},
+#endif
{0, 0, 0, 0}
};
@@ -392,6 +402,10 @@ void show_help(const char* argv0)
<< " --metadata-track-uri URI uses the URI identifier for the metadata track (experimental)\n"
<< " --set-gimi-track-id ID set the GIMI track ID for the visual track (experimental)\n"
<< " --sai-data-file FILE use the specified FILE as input data for the video frames SAI data\n"
+#endif
+#if HEIF_WITH_OMAF
+ << "omnidirectional imagery:\n"
+ << " --image-projection proj set the image projection (0 = equirectangular, 1 = cube map)\n"
#endif
;
}
@@ -1605,6 +1619,19 @@ int main(int argc, char** argv)
case OPTION_SEQUENCES_SAI_DATA_FILE:
option_sai_data_file = optarg;
break;
+#if HEIF_WITH_OMAF
+ case OPTION_SET_IMAGE_PROJECTION:
+ option_image_projection = atoi(optarg);
+ if (option_image_projection == 0) {
+ image_projection = heif_image_projection::equirectangular;
+ } else if (option_image_projection == 1) {
+ image_projection = heif_image_projection::cube_map;
+ } else {
+ std::cerr << "image projection must be 0 or 1\n";
+ return 5;
+ }
+ break;
+#endif
}
}
@@ -2019,6 +2046,12 @@ int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding
heif_image_handle_set_pixel_aspect_ratio(handle, pasp->h, pasp->v);
}
+#if HEIF_WITH_OMAF
+ if (image_projection != heif_image_projection::flat) {
+ heif_image_handle_set_image_projection(handle, image_projection);
+ }
+#endif
+
if (is_primary_image) {
heif_context_set_primary_image(context, handle);
}
diff --git a/examples/heif_info.cc b/examples/heif_info.cc
index 8ab72b38..c4dc685a 100644
--- a/examples/heif_info.cc
+++ b/examples/heif_info.cc
@@ -778,6 +778,28 @@ int main(int argc, char** argv)
properties_shown = true;
}
+#if HEIF_WITH_OMAF
+ // --- OMAF
+
+ if (heif_image_handle_has_image_projection(handle)) {
+ heif_image_projection projection = heif_image_handle_get_image_projection(handle);
+ std::cout << " image projection: ";
+ switch (projection)
+ {
+ case heif_image_projection::equirectangular:
+ std::cout << "equirectangular";
+ break;
+ case heif_image_projection::cube_map:
+ std::cout << "cube map";
+ default:
+ std::cout << "(unknown)";
+ break;
+ }
+ std::cout << "\n";
+ properties_shown = true;
+ }
+#endif
+
if (!properties_shown) {
std::cout << "none\n";
}
diff --git a/libheif.pc.in b/libheif.pc.in
index 1057594d..8a62403c 100644
--- a/libheif.pc.in
+++ b/libheif.pc.in
@@ -12,4 +12,4 @@ Requires.private: @REQUIRES_PRIVATE@
Libs: -L${libdir} -lheif
Libs.private: @LIBS_PRIVATE@
Cflags: -I${includedir}
-Cflags.private: -DLIBHEIF_STATIC_BUILD
+Cflags.private: -DLIBHEIF_STATIC_BUILD -DHEIF_WITH_OMAF=@WITH_OMAF@
diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index 97eca435..2d65a5b0 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -330,6 +330,13 @@ if (ENABLE_EXPERIMENTAL_MINI_FORMAT)
mini.cc)
endif ()
+if (HEIF_WITH_OMAF)
+ target_compile_definitions(heif PUBLIC HEIF_WITH_OMAF=1)
+ target_sources(heif PRIVATE
+ omaf_boxes.h
+ omaf_boxes.cc)
+endif ()
+
write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake COMPATIBILITY ExactVersion)
install(TARGETS heif EXPORT ${PROJECT_NAME}-config
diff --git a/libheif/api/libheif/heif_image.cc b/libheif/api/libheif/heif_image.cc
index d0af524d..1f82dfed 100644
--- a/libheif/api/libheif/heif_image.cc
+++ b/libheif/api/libheif/heif_image.cc
@@ -307,6 +307,17 @@ void heif_image_handle_set_pixel_aspect_ratio(heif_image_handle* handle, uint32_
handle->image->set_pixel_ratio(aspect_h, aspect_v);
}
+#if HEIF_WITH_OMAF
+heif_image_projection heif_image_get_image_projection(const heif_image* image)
+{
+ return image->image->get_image_projection();
+}
+
+void heif_image_set_image_projection(const heif_image* image, heif_image_projection image_projection)
+{
+ return image->image->set_image_projection(image_projection);
+}
+#endif
heif_error heif_image_create(int width, int height,
heif_colorspace colorspace,
diff --git a/libheif/api/libheif/heif_image.h b/libheif/api/libheif/heif_image.h
index 7de5780e..b5b95a0b 100644
--- a/libheif/api/libheif/heif_image.h
+++ b/libheif/api/libheif/heif_image.h
@@ -94,6 +94,40 @@ typedef enum heif_channel
heif_channel_unknown = 65535
} heif_channel;
+#if HEIF_WITH_OMAF
+/**
+ * Image projection.
+ *
+ * The image projection for most images is flat - it is projected as intended to be shown on
+ * a flat screen or print. For immersive or omnidirectional media (e.g. VR headsets, or
+ * equivalent), there are alternatives such as an equirectangular projection or cubemap projection.
+ *
+ * See ISO/IEC 23090-2 "Omnidirectional media format" for more information.
+ */
+typedef enum heif_image_projection
+{
+ /**
+ * Equirectangular projection.
+ */
+ equirectangular = 0x00,
+
+ /**
+ * Cube map.
+ */
+ cube_map = 0x01,
+
+ /* Values 2 through 31 are reserved in ISO/IEC 23090-2:2023 Table 10. */
+ /**
+ * Projection is specified, but not recognised.
+ */
+ unknown_other = 0xFE,
+
+ /**
+ * Flat projection, assumed if no projection information provided.
+ */
+ flat = 0xFF,
+} heif_image_projection;
+#endif
// An heif_image contains a decoded pixel image in various colorspaces, chroma formats,
// and bit depths.
@@ -266,6 +300,13 @@ void heif_image_set_pixel_aspect_ratio(heif_image*, uint32_t aspect_h, uint32_t
LIBHEIF_API
void heif_image_handle_set_pixel_aspect_ratio(heif_image_handle*, uint32_t aspect_h, uint32_t aspect_v);
+#if HEIF_WITH_OMAF
+LIBHEIF_API
+heif_image_projection heif_image_get_image_projection(const heif_image*);
+
+LIBHEIF_API
+void heif_image_set_image_projection(const heif_image*, heif_image_projection image_projection);
+#endif
// --- heif_image allocation
diff --git a/libheif/api/libheif/heif_properties.cc b/libheif/api/libheif/heif_properties.cc
index bc18e416..a5be076f 100644
--- a/libheif/api/libheif/heif_properties.cc
+++ b/libheif/api/libheif/heif_properties.cc
@@ -458,3 +458,23 @@ heif_error heif_camera_extrinsic_matrix_get_rotation_matrix(const heif_camera_ex
return heif_error_success;
}
+#if HEIF_WITH_OMAF
+int heif_image_handle_has_image_projection(const heif_image_handle* handle)
+{
+ if (!handle) {
+ return false;
+ }
+
+ return handle->image->has_image_projection();
+}
+
+heif_image_projection heif_image_handle_get_image_projection(const heif_image_handle* handle)
+{
+ return handle->image->get_image_projection();
+}
+
+void heif_image_handle_set_image_projection(const heif_image_handle* handle, heif_image_projection image_projection)
+{
+ return handle->image->set_image_projection(image_projection);
+}
+#endif
diff --git a/libheif/api/libheif/heif_properties.h b/libheif/api/libheif/heif_properties.h
index ceda2aa2..5c20befc 100644
--- a/libheif/api/libheif/heif_properties.h
+++ b/libheif/api/libheif/heif_properties.h
@@ -228,6 +228,18 @@ LIBHEIF_API
heif_error heif_camera_extrinsic_matrix_get_rotation_matrix(const heif_camera_extrinsic_matrix*,
double* out_matrix_row_major);
+#if HEIF_WITH_OMAF
+// ------------------------- projection information -------------------------
+LIBHEIF_API
+int heif_image_handle_has_image_projection(const heif_image_handle* handle);
+
+LIBHEIF_API
+heif_image_projection heif_image_handle_get_image_projection(const heif_image_handle* handle);
+
+LIBHEIF_API
+void heif_image_handle_set_image_projection(const heif_image_handle* handle, heif_image_projection image_projection);
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/libheif/box.cc b/libheif/box.cc
index 3ffabf3c..bae957fb 100644
--- a/libheif/box.cc
+++ b/libheif/box.cc
@@ -884,6 +884,13 @@ Error Box::read(BitstreamRange& range, std::shared_ptr<Box>* result, const heif_
box = std::make_shared<Box_sdtp>();
break;
+#if HEIF_WITH_OMAF
+ // OMAF
+ case fourcc("prfr"):
+ box = std::make_shared<Box_prfr>();
+ break;
+#endif
+
default:
box = std::make_shared<Box_other>(hdr.get_short_type());
break;
diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc
index fca791a2..cf0a60ed 100644
--- a/libheif/color-conversion/colorconversion.cc
+++ b/libheif/color-conversion/colorconversion.cc
@@ -484,6 +484,10 @@ Result<std::shared_ptr<HeifPixelImage>> ColorConversionPipeline::convert_image(c
out->set_sample_duration(in->get_sample_duration());
+#if HEIF_WITH_OMAF
+ out->set_image_projection(in->get_image_projection());
+#endif
+
const auto& warnings = in->get_warnings();
for (const auto& warning : warnings) {
out->add_warning(warning);
diff --git a/libheif/context.cc b/libheif/context.cc
index a2be4750..d72cf54d 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -690,6 +690,13 @@ Error HeifContext::interpret_heif_file_images()
if (auto box_gimi_content_id = image->get_property<Box_gimi_content_id>()) {
image->set_gimi_sample_content_id(box_gimi_content_id->get_content_id());
}
+
+#if HEIF_WITH_OMAF
+ // add image projection information
+ if (auto prfr = image->get_property<Box_prfr>()) {
+ image->set_image_projection(prfr->get_image_projection());
+ }
+#endif
}
diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc
index 54ddb716..71a725de 100644
--- a/libheif/image-items/image_item.cc
+++ b/libheif/image-items/image_item.cc
@@ -680,6 +680,14 @@ void ImageItem::set_color_profile_icc(const std::shared_ptr<const color_profile_
add_property(get_colr_box_icc(), false);
}
+#if HEIF_WITH_OMAF
+void ImageItem::set_image_projection(heif_image_projection projection)
+{
+ ImageExtraData::set_image_projection(projection);
+ add_property(get_prfr_box(), true);
+}
+#endif
+
Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_image(const heif_decoding_options& options,
bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
@@ -910,8 +918,17 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_image(const heif_decod
if (gimi_content_id) {
img->set_gimi_sample_content_id(gimi_content_id->get_content_id());
}
+
+#if HEIF_WITH_OMAF
+ // Image projection (OMAF)
+ auto prfr = get_property<Box_prfr>();
+ if (prfr) {
+ img->set_image_projection(prfr->get_image_projection());
+ }
+#endif
}
+
return img;
}
diff --git a/libheif/image-items/image_item.h b/libheif/image-items/image_item.h
index a5ac0ac3..5932614f 100644
--- a/libheif/image-items/image_item.h
+++ b/libheif/image-items/image_item.h
@@ -287,6 +287,10 @@ public:
void set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile) override;
+#if HEIF_WITH_OMAF
+ void set_image_projection(heif_image_projection image_projection) override;
+#endif
+
// --- miaf
// TODO: we should have a function that checks all MIAF constraints and sets the compatibility flag.
diff --git a/libheif/omaf_boxes.cc b/libheif/omaf_boxes.cc
new file mode 100644
index 00000000..a264aa5f
--- /dev/null
+++ b/libheif/omaf_boxes.cc
@@ -0,0 +1,93 @@
+/*
+ * libheif OMAF (ISO/IEC 23090-2)
+ *
+ * Copyright (c) 2026 Brad Hards <bradh@frogmouth.net>
+ *
+ * This file is part of libheif.
+ *
+ * libheif is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * libheif is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libheif. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "omaf_boxes.h"
+
+#include <memory>
+#include <string>
+
+
+Error Box_prfr::parse(BitstreamRange& range, const heif_security_limits* limits)
+{
+ parse_full_box_header(range);
+
+ if (get_version() > 0) {
+ return unsupported_version_error("prfr");
+ }
+
+ uint8_t projection_type = (range.read8() & 0x1F);
+ switch (projection_type)
+ {
+ case 0x00:
+ m_projection = heif_image_projection::equirectangular;
+ break;
+ case 0x01:
+ m_projection = heif_image_projection::cube_map;
+ break;
+ default:
+ m_projection = heif_image_projection::unknown_other;
+ break;
+ }
+ return range.get_error();
+}
+
+std::string Box_prfr::dump(Indent& indent) const
+{
+ std::ostringstream sstr;
+ sstr << Box::dump(indent);
+ sstr << indent << "projection_type: " << m_projection << "\n";
+ return sstr.str();
+}
+
+Error Box_prfr::write(StreamWriter& writer) const
+{
+ size_t box_start = reserve_box_header_space(writer);
+ switch (m_projection) {
+ case heif_image_projection::equirectangular:
+ writer.write8(0x00);
+ break;
+ case heif_image_projection::cube_map:
+ writer.write8(0x01);
+ break;
+ default:
+ return {
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Unsupported image projection value."
+ };
+ }
+ prepend_header(writer, box_start);
+ return Error::Ok;
+}
+
+Error Box_prfr::set_image_projection(heif_image_projection projection)
+{
+ if ((projection == heif_image_projection::equirectangular) || (projection == heif_image_projection::cube_map)) {
+ m_projection = projection;
+ return Error::Ok;
+ } else {
+ return {
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Unsupported image projection value."
+ };
+ }
+}
diff --git a/libheif/omaf_boxes.h b/libheif/omaf_boxes.h
new file mode 100644
index 00000000..0d82d659
--- /dev/null
+++ b/libheif/omaf_boxes.h
@@ -0,0 +1,60 @@
+/*
+ * libheif OMAF (ISO/IEC 23090-2)
+ *
+ * Copyright (c) 2026 Brad Hards <bradh@frogmouth.net>
+ *
+ * This file is part of libheif.
+ *
+ * libheif is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * libheif is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libheif. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBHEIF_MINI_H
+#define LIBHEIF_MINI_H
+
+#include "libheif/heif.h"
+#include "box.h"
+
+#include <memory>
+#include <string>
+
+// Projection format for OMAF
+// See ISO/IEC 23090-2:2023 Section 7.9.3
+class Box_prfr : public FullBox
+{
+public:
+ Box_prfr()
+ {
+ set_short_type(fourcc("prfr"));
+ }
+
+ heif_image_projection get_image_projection() const { return m_projection; }
+
+ Error set_image_projection(heif_image_projection projection);
+
+ std::string dump(Indent&) const override;
+
+ const char* debug_box_name() const override { return "Projection Format"; }
+
+ [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; }
+
+ Error write(StreamWriter& writer) const override;
+
+protected:
+ Error parse(BitstreamRange& range, const heif_security_limits*) override;
+
+private:
+ heif_image_projection m_projection;
+};
+
+#endif
\ No newline at end of file
diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index ccc42cc1..b481966c 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -212,6 +212,19 @@ std::shared_ptr<Box_colr> ImageExtraData::get_colr_box_icc() const
return colr;
}
+#if HEIF_WITH_OMAF
+std::shared_ptr<Box_prfr> ImageExtraData::get_prfr_box() const
+{
+ if (!has_image_projection()) {
+ return {};
+ }
+
+ auto prfr = std::make_shared<Box_prfr>();
+ prfr->set_image_projection(get_image_projection());
+
+ return prfr;
+}
+#endif
std::vector<std::shared_ptr<Box>> ImageExtraData::generate_property_boxes(bool generate_colr_boxes) const
{
@@ -267,6 +280,14 @@ std::vector<std::shared_ptr<Box>> ImageExtraData::generate_property_boxes(bool g
}
}
+#if HEIF_WITH_OMAF
+ if (has_image_projection()) {
+ auto prfr = std::make_shared<Box_prfr>();
+ prfr->set_image_projection(get_image_projection());
+ properties.push_back(prfr);
+ }
+#endif
+
return properties;
}
@@ -1875,6 +1896,10 @@ void HeifPixelImage::forward_all_metadata_from(const std::shared_ptr<const HeifP
// TODO: TAI timestamp and contentID (once we merge that branch)
// TODO: should we also forward the warnings? It might be better to do that in ImageItem_Grid.
+
+#if HEIF_WITH_OMAF
+ set_image_projection(src_image->get_image_projection());
+#endif
}
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 396c10a6..165302f1 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -26,6 +26,9 @@
#include "error.h"
#include "nclx.h"
#include <libheif/heif_experimental.h>
+#if HEIF_WITH_OMAF
+#include "omaf_boxes.h"
+#endif
#include "security_limits.h"
#include <vector>
@@ -177,6 +180,20 @@ public:
std::string get_gimi_sample_content_id() const { assert(has_gimi_sample_content_id()); return *m_gimi_sample_content_id; }
+#if HEIF_WITH_OMAF
+ bool has_image_projection() const {
+ return (m_image_projection != heif_image_projection::flat);
+ }
+
+ const heif_image_projection get_image_projection() const {
+ return m_image_projection;
+ }
+
+ virtual void set_image_projection(const heif_image_projection projection) {
+ m_image_projection = projection;
+ }
+#endif
+
private:
bool m_premultiplied_alpha = false;
nclx_profile m_color_profile_nclx = nclx_profile::undefined();
@@ -191,6 +208,10 @@ private:
std::optional<std::string> m_gimi_sample_content_id;
+#if HEIF_WITH_OMAF
+ heif_image_projection m_image_projection = heif_image_projection::flat;
+#endif
+
protected:
std::shared_ptr<Box_clli> get_clli_box() const;
@@ -201,6 +222,10 @@ protected:
std::shared_ptr<Box_colr> get_colr_box_nclx() const;
std::shared_ptr<Box_colr> get_colr_box_icc() const;
+
+#if HEIF_WITH_OMAF
+ std::shared_ptr<Box_prfr> get_prfr_box() const;
+#endif
};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 443f584f..557e7424 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -81,6 +81,13 @@ if (ENABLE_EXPERIMENTAL_MINI_FORMAT)
add_libheif_test(mini_decode)
endif()
+if (HEIF_WITH_OMAF)
+ if (NOT WITH_REDUCED_VISIBILITY)
+ add_libheif_test(omaf_boxes)
+ endif()
+ add_libheif_test(omaf)
+endif()
+
if (WITH_UNCOMPRESSED_CODEC)
add_libheif_test(uncompressed_decode)
add_libheif_test(uncompressed_decode_mono)
diff --git a/tests/omaf.cc b/tests/omaf.cc
new file mode 100644
index 00000000..9ebc4a3e
--- /dev/null
+++ b/tests/omaf.cc
@@ -0,0 +1,92 @@
+/*
+ libheif OMAF (ISO/IEC 23090-2) unit tests
+
+ MIT License
+
+ Copyright (c) 2026 Brad Hards <bradh@frogmouth.net>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#include "catch_amalgamated.hpp"
+#include "libheif/heif.h"
+#include "api_structs.h"
+#include "pixelimage.h"
+
+#include "test_utils.h"
+
+#include <string.h>
+
+static heif_encoding_options * set_encoding_options()
+{
+ heif_encoding_options * options = heif_encoding_options_alloc();
+ options->macOS_compatibility_workaround = false;
+ options->macOS_compatibility_workaround_no_nclx_profile = true;
+ options->image_orientation = heif_orientation_normal;
+ return options;
+}
+
+static void do_encode(heif_image* input_image, const char* filename, heif_image_projection projection)
+{
+ REQUIRE(input_image != nullptr);
+ heif_init(nullptr);
+ heif_context *ctx = heif_context_alloc();
+ heif_encoder *encoder;
+ heif_error err;
+ err = heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder);
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_encoding_options *options = set_encoding_options();
+
+ heif_image_handle *output_image_handle;
+
+ err = heif_context_encode_image(ctx, input_image, encoder, options, &output_image_handle);
+ REQUIRE(err.code == heif_error_Ok);
+ heif_image_handle_set_image_projection(output_image_handle, projection);
+ err = heif_context_write_to_file(ctx, filename);
+ REQUIRE(err.code == heif_error_Ok);
+
+ heif_image_handle_release(output_image_handle);
+ heif_encoding_options_free(options);
+ heif_encoder_release(encoder);
+ heif_image_release(input_image);
+
+ heif_context_free(ctx);
+
+ heif_context *readbackCtx = get_context_for_local_file(filename);
+ heif_image_handle *readbackHandle = get_primary_image_handle(readbackCtx);
+ heif_image_projection readbackProjection = heif_image_handle_get_image_projection(readbackHandle);
+ REQUIRE(readbackProjection == projection);
+ heif_image_handle_release(readbackHandle);
+ heif_context_free(readbackCtx);
+
+ heif_deinit();
+}
+
+TEST_CASE("Encode OMAF HEIC")
+{
+ heif_image *input_image = createImage_RGB_planar();
+ do_encode(input_image, "encode_omaf_equirectangular.heic", heif_image_projection::equirectangular);
+}
+
+TEST_CASE("Encode OMAF HEIC Cubemap")
+{
+ heif_image *input_image = createImage_RGB_planar();
+ do_encode(input_image, "encode_omaf_cubemap.heic", heif_image_projection::cube_map);
+}
\ No newline at end of file
diff --git a/tests/omaf_boxes.cc b/tests/omaf_boxes.cc
new file mode 100644
index 00000000..73972c6b
--- /dev/null
+++ b/tests/omaf_boxes.cc
@@ -0,0 +1,62 @@
+/*
+ libheif OMAF (ISO/IEC 23090-2) unit tests
+
+ MIT License
+
+ Copyright (c) 2026 Brad Hards <bradh@frogmouth.net>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#include "catch_amalgamated.hpp"
+#include "omaf_boxes.h"
+#include "error.h"
+#include <cstdint>
+#include <iostream>
+#include <memory>
+
+
+TEST_CASE("prfr") {
+ std::vector<uint8_t> byteArray{0x00, 0x00, 0x00, 0x0d, 0x70, 0x72, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x01};
+
+ auto reader = std::make_shared<StreamReader_memory>(byteArray.data(),
+ byteArray.size(), false);
+
+ BitstreamRange range(reader, byteArray.size());
+ std::shared_ptr<Box> box;
+ Error error = Box::read(range, &box, heif_get_global_security_limits());
+ REQUIRE(error == Error::Ok);
+ REQUIRE(range.error() == 0);
+
+ REQUIRE(box->get_short_type() == fourcc("prfr"));
+ REQUIRE(box->get_type_string() == "prfr");
+ std::shared_ptr<Box_prfr> prfr = std::dynamic_pointer_cast<Box_prfr>(box);
+ REQUIRE(prfr->get_image_projection() == heif_image_projection::cube_map);
+ Indent indent;
+ std::string dumpResult = box->dump(indent);
+ REQUIRE(dumpResult == "Box: prfr ----- (Projection Format)\n"
+ "size: 13 (header size: 12)\n"
+ "projection_type: 1\n");
+
+ StreamWriter writer;
+ Error err = prfr->write(writer);
+ REQUIRE(err.error_code == heif_error_Ok);
+ const std::vector<uint8_t> bytes = writer.get_data();
+ REQUIRE(bytes == byteArray);
+}