44
55
66#include < OpenImageIO/filesystem.h>
7+ #include < OpenImageIO/fmath.h>
78#include < OpenImageIO/imageio.h>
9+ #include < OpenImageIO/platform.h>
810#include < OpenImageIO/tiffutils.h>
911
1012#include < libheif/heif_cxx.h>
@@ -26,7 +28,11 @@ class HeifOutput final : public ImageOutput {
2628 const char * format_name (void ) const override { return " heif" ; }
2729 int supports (string_view feature) const override
2830 {
29- return feature == " alpha" || feature == " exif" || feature == " tiles" ;
31+ return feature == " alpha" || feature == " exif" || feature == " tiles"
32+ #if LIBHEIF_HAVE_VERSION(1, 9, 0)
33+ || feature == " cicp"
34+ #endif
35+ ;
3036 }
3137 bool open (const std::string& name, const ImageSpec& spec,
3238 OpenMode mode) override ;
@@ -45,6 +51,7 @@ class HeifOutput final : public ImageOutput {
4551 heif::Encoder m_encoder { heif_compression_HEVC };
4652 std::vector<unsigned char > scratch;
4753 std::vector<unsigned char > m_tilebuffer;
54+ int m_bitdepth = 0 ;
4855};
4956
5057
@@ -104,19 +111,33 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec,
104111
105112 m_filename = name;
106113
107- m_spec.set_format (TypeUInt8); // Only uint8 for now
114+ m_bitdepth = m_spec.format .size () > TypeUInt8.size () ? 10 : 8 ;
115+ m_bitdepth = m_spec.get_int_attribute (" oiio:BitsPerSample" , m_bitdepth);
116+ if (m_bitdepth == 10 || m_bitdepth == 12 ) {
117+ m_spec.set_format (TypeUInt16);
118+ } else if (m_bitdepth == 8 ) {
119+ m_spec.set_format (TypeUInt8);
120+ } else {
121+ errorfmt (" Unsupported bit depth {}" , m_bitdepth);
122+ return false ;
123+ }
108124
109125 try {
110126 m_ctx.reset (new heif::Context);
111127 m_himage = heif::Image ();
112128 static heif_chroma chromas[/* nchannels*/ ]
113129 = { heif_chroma_undefined, heif_chroma_monochrome,
114- heif_chroma_undefined, heif_chroma_interleaved_RGB,
115- heif_chroma_interleaved_RGBA };
130+ heif_chroma_undefined,
131+ (m_bitdepth == 8 ) ? heif_chroma_interleaved_RGB
132+ : littleendian () ? heif_chroma_interleaved_RRGGBB_LE
133+ : heif_chroma_interleaved_RRGGBB_BE,
134+ (m_bitdepth == 8 ) ? heif_chroma_interleaved_RGBA
135+ : littleendian () ? heif_chroma_interleaved_RRGGBBAA_LE
136+ : heif_chroma_interleaved_RRGGBBAA_BE };
116137 m_himage.create (newspec.width , newspec.height , heif_colorspace_RGB,
117138 chromas[m_spec.nchannels ]);
118139 m_himage.add_plane (heif_channel_interleaved, newspec.width ,
119- newspec.height , 8 * m_spec. nchannels /* bit depth */ );
140+ newspec.height , m_bitdepth );
120141
121142 m_encoder = heif::Encoder (heif_compression_HEVC);
122143 auto compqual = m_spec.decode_compression_metadata (" " , 75 );
@@ -161,7 +182,22 @@ HeifOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data,
161182 uint8_t * hdata = m_himage.get_plane (heif_channel_interleaved, &hystride);
162183#endif
163184 hdata += hystride * (y - m_spec.y );
164- memcpy (hdata, data, hystride);
185+ if (m_bitdepth == 10 || m_bitdepth == 12 ) {
186+ const uint16_t * data16 = static_cast <const uint16_t *>(data);
187+ uint16_t * hdata16 = reinterpret_cast <uint16_t *>(hdata);
188+ const size_t num_values = m_spec.width * m_spec.nchannels ;
189+ if (m_bitdepth == 10 ) {
190+ for (size_t i = 0 ; i < num_values; ++i) {
191+ hdata16[i] = bit_range_convert<16 , 10 >(data16[i]);
192+ }
193+ } else {
194+ for (size_t i = 0 ; i < num_values; ++i) {
195+ hdata16[i] = bit_range_convert<16 , 12 >(data16[i]);
196+ }
197+ }
198+ } else {
199+ memcpy (hdata, data, hystride);
200+ }
165201 return true ;
166202}
167203
@@ -207,8 +243,30 @@ HeifOutput::close()
207243 } else if (compqual.first == " none" ) {
208244 m_encoder.set_lossless (true );
209245 }
246+ heif::Context::EncodingOptions options;
247+ #if LIBHEIF_HAVE_VERSION(1, 9, 0)
248+ // Write CICP. we can only set output_nclx_profile with the C API.
249+ std::unique_ptr<heif_color_profile_nclx,
250+ void (*)(heif_color_profile_nclx*)>
251+ nclx (heif_nclx_color_profile_alloc (), heif_nclx_color_profile_free);
252+ const ParamValue* p = m_spec.find_attribute (" CICP" ,
253+ TypeDesc (TypeDesc::INT, 4 ));
254+ if (p) {
255+ const int * cicp = static_cast <const int *>(p->data ());
256+ nclx->color_primaries = heif_color_primaries (cicp[0 ]);
257+ nclx->transfer_characteristics = heif_transfer_characteristics (
258+ cicp[1 ]);
259+ nclx->matrix_coefficients = heif_matrix_coefficients (cicp[2 ]);
260+ nclx->full_range_flag = cicp[3 ];
261+ options.output_nclx_profile = nclx.get ();
262+ // Chroma subsampling is incompatible with RGB.
263+ if (nclx->matrix_coefficients == heif_matrix_coefficients_RGB_GBR) {
264+ m_encoder.set_string_parameter (" chroma" , " 444" );
265+ }
266+ }
267+ #endif
210268 encode_exif (m_spec, exifblob, endian::big);
211- m_ihandle = m_ctx->encode_image (m_himage, m_encoder);
269+ m_ihandle = m_ctx->encode_image (m_himage, m_encoder, options );
212270 std::vector<char > head { ' E' , ' x' , ' i' , ' f' , 0 , 0 };
213271 exifblob.insert (exifblob.begin (), head.begin (), head.end ());
214272 try {
0 commit comments