Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions src/doc/builtinplugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,12 @@ preferred except when legacy file access is required.
- string
- Color space (see Section :ref:`sec-metadata-color`). We currently
assume that any RGBE files encountered are linear with sRGB primaries.
* - ``CICP``
- int[4]
- Coding-independent code points to describe the color profile.
* - ``oiio:BitsPerSample``
- int
- Bits per sample in the file: 8, 10 or 12.
* - ``heif:Orientation``
- int
- If the configuration option ``heif:reorient`` is nonzero and
Expand Down
79 changes: 69 additions & 10 deletions src/heif.imageio/heifinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ class HeifInput final : public ImageInput {
const char* format_name(void) const override { return "heif"; }
int supports(string_view feature) const override
{
return feature == "exif";
return feature == "exif"
#if LIBHEIF_HAVE_VERSION(1, 9, 0)
|| feature == "cicp"
#endif
;
}
bool valid_file(const std::string& filename) const override;
bool open(const std::string& name, ImageSpec& newspec) override;
Expand All @@ -53,6 +57,7 @@ class HeifInput final : public ImageInput {
std::string m_filename;
int m_subimage = -1;
int m_num_subimages = 0;
int m_bitdepth = 0;
int m_has_alpha = false;
bool m_associated_alpha = true;
bool m_keep_unassociated_alpha = false;
Expand Down Expand Up @@ -203,11 +208,26 @@ HeifInput::seek_subimage(int subimage, int miplevel)
return false;
}

auto id = (subimage == 0) ? m_primary_id : m_item_ids[subimage - 1];
m_ihandle = m_ctx->get_image_handle(id);
auto id = (subimage == 0) ? m_primary_id : m_item_ids[subimage - 1];
m_ihandle = m_ctx->get_image_handle(id);

m_bitdepth = m_ihandle.get_luma_bits_per_pixel();
if (m_bitdepth < 0) {
errorfmt("Image has undefined bit depth");
m_ctx.reset();
return false;
} else if (!(m_bitdepth == 8 || m_bitdepth == 10 || m_bitdepth == 12)) {
errorfmt("Image has unsupported bit depth {}", m_bitdepth);
m_ctx.reset();
return false;
}

m_has_alpha = m_ihandle.has_alpha_channel();
auto chroma = m_has_alpha ? heif_chroma_interleaved_RGBA
: heif_chroma_interleaved_RGB;
auto chroma = m_has_alpha ? (m_bitdepth > 8)
? heif_chroma_interleaved_RRGGBBAA_LE
: heif_chroma_interleaved_RGBA
: (m_bitdepth > 8) ? heif_chroma_interleaved_RRGGBB_LE
: heif_chroma_interleaved_RGB;
#if 0
try {
m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma);
Expand Down Expand Up @@ -238,13 +258,40 @@ HeifInput::seek_subimage(int subimage, int miplevel)
}
#endif

int bits = m_himage.get_bits_per_pixel(heif_channel_interleaved);
m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved),
m_himage.get_height(heif_channel_interleaved), bits / 8,
TypeUInt8);
m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved),
m_himage.get_height(heif_channel_interleaved),
m_has_alpha ? 4 : 3,
(m_bitdepth > 8) ? TypeUInt16 : TypeUInt8);

if (m_bitdepth > 8) {
m_spec.attribute("oiio:BitsPerSample", m_bitdepth);
}
m_spec.set_colorspace("srgb_rec709_scene");

#if LIBHEIF_HAVE_VERSION(1, 9, 0)
// Read CICP. Have to use the C API to get it from the image handle,
// the one on the decoded image is not what was written in the file.
enum heif_color_profile_type profile_type
= heif_image_handle_get_color_profile_type(
m_ihandle.get_raw_image_handle());
if (profile_type == heif_color_profile_type_nclx) {
heif_color_profile_nclx* nclx = nullptr;
const heif_error err = heif_image_handle_get_nclx_color_profile(
m_ihandle.get_raw_image_handle(), &nclx);

if (nclx) {
if (err.code == heif_error_Ok) {
const int cicp[4] = { int(nclx->color_primaries),
int(nclx->transfer_characteristics),
int(nclx->matrix_coefficients),
int(nclx->full_range_flag ? 1 : 0) };
m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
}
heif_nclx_color_profile_free(nclx);
}
}
#endif

#if LIBHEIF_HAVE_VERSION(1, 12, 0)
// Libheif >= 1.12 added API call to find out if the image is associated
// alpha (i.e. colors are premultiplied).
Expand Down Expand Up @@ -402,7 +449,19 @@ HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
return false;
}
hdata += (y - m_spec.y) * ystride;
memcpy(data, hdata, m_spec.width * m_spec.pixel_bytes());
if (m_spec.format == TypeUInt16) {
// Convert from 10 or 12 bits to 16 bits. Read little endian
// zero padded bits, and shift to scale up.
const uint8_t* in = hdata;
uint16_t* out = static_cast<uint16_t*>(data);
const int bitshift = 16 - m_bitdepth;
const size_t num_values = m_spec.width * m_spec.nchannels;
for (size_t i = 0; i < num_values; i++, out++, in += 2) {
*out = uint16_t((in[1] << 8) | in[0]) << bitshift;
}
} else {
memcpy(data, hdata, m_spec.width * m_spec.pixel_bytes());
}
return true;
}

Expand Down
63 changes: 56 additions & 7 deletions src/heif.imageio/heifoutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ class HeifOutput final : public ImageOutput {
const char* format_name(void) const override { return "heif"; }
int supports(string_view feature) const override
{
return feature == "alpha" || feature == "exif" || feature == "tiles";
return feature == "alpha" || feature == "exif" || feature == "tiles"
#if LIBHEIF_HAVE_VERSION(1, 9, 0)
|| feature == "cicp"
#endif
;
}
bool open(const std::string& name, const ImageSpec& spec,
OpenMode mode) override;
Expand All @@ -45,6 +49,7 @@ class HeifOutput final : public ImageOutput {
heif::Encoder m_encoder { heif_compression_HEVC };
std::vector<unsigned char> scratch;
std::vector<unsigned char> m_tilebuffer;
int m_bitdepth = 0;
};


Expand Down Expand Up @@ -104,19 +109,31 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec,

m_filename = name;

m_spec.set_format(TypeUInt8); // Only uint8 for now
m_bitdepth = m_spec.format.size() > TypeUInt8.size() ? 10 : 8;
m_bitdepth = m_spec.get_int_attribute("oiio:BitsPerSample", m_bitdepth);
if (m_bitdepth == 10 || m_bitdepth == 12) {
m_spec.set_format(TypeUInt16);
} else if (m_bitdepth == 8) {
m_spec.set_format(TypeUInt8);
} else {
errorfmt("Unsupported bit depth {}", m_bitdepth);
return false;
}

try {
m_ctx.reset(new heif::Context);
m_himage = heif::Image();
static heif_chroma chromas[/*nchannels*/]
= { heif_chroma_undefined, heif_chroma_monochrome,
heif_chroma_undefined, heif_chroma_interleaved_RGB,
heif_chroma_interleaved_RGBA };
heif_chroma_undefined,
(m_bitdepth == 8) ? heif_chroma_interleaved_RGB
: heif_chroma_interleaved_RRGGBB_LE,
(m_bitdepth == 8) ? heif_chroma_interleaved_RGBA
: heif_chroma_interleaved_RRGGBBAA_LE };
m_himage.create(newspec.width, newspec.height, heif_colorspace_RGB,
chromas[m_spec.nchannels]);
m_himage.add_plane(heif_channel_interleaved, newspec.width,
newspec.height, 8 * m_spec.nchannels /*bit depth*/);
newspec.height, m_bitdepth);

m_encoder = heif::Encoder(heif_compression_HEVC);
auto compqual = m_spec.decode_compression_metadata("", 75);
Expand Down Expand Up @@ -161,7 +178,21 @@ HeifOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data,
uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, &hystride);
#endif
hdata += hystride * (y - m_spec.y);
memcpy(hdata, data, hystride);
if (m_spec.format == TypeUInt16) {
// Convert from 16 bits to 10 or 12 bits. Shift to scale down and
// output zero padded little endian.
const uint16_t* in = static_cast<const uint16_t*>(data);
uint8_t* out = hdata;
const int bitshift = 16 - m_bitdepth;
const size_t num_values = m_spec.width * m_spec.nchannels;
for (size_t i = 0; i < num_values; i++, out += 2, in++) {
const uint16_t v = *in >> bitshift;
out[0] = (uint8_t)(v & 0xFF);
out[1] = (uint8_t)(v >> 8);
}
} else {
memcpy(hdata, data, hystride);
}
return true;
}

Expand Down Expand Up @@ -207,8 +238,26 @@ HeifOutput::close()
} else if (compqual.first == "none") {
m_encoder.set_lossless(true);
}
heif::Context::EncodingOptions options;
#if LIBHEIF_HAVE_VERSION(1, 9, 0)
// Write CICP. we can only set output_nclx_profile with the C API.
std::unique_ptr<heif_color_profile_nclx,
void (*)(heif_color_profile_nclx*)>
nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free);
const ParamValue* p = m_spec.find_attribute("CICP",
TypeDesc(TypeDesc::INT, 4));
if (p) {
const int* cicp = static_cast<const int*>(p->data());
nclx->color_primaries = heif_color_primaries(cicp[0]);
nclx->transfer_characteristics = heif_transfer_characteristics(
cicp[1]);
nclx->matrix_coefficients = heif_matrix_coefficients(cicp[2]);
nclx->full_range_flag = cicp[3];
options.output_nclx_profile = nclx.get();
}
#endif
encode_exif(m_spec, exifblob, endian::big);
m_ihandle = m_ctx->encode_image(m_himage, m_encoder);
m_ihandle = m_ctx->encode_image(m_himage, m_encoder, options);
std::vector<char> head { 'E', 'x', 'i', 'f', 0, 0 };
exifblob.insert(exifblob.begin(), head.begin(), head.end());
try {
Expand Down
28 changes: 28 additions & 0 deletions testsuite/heif/ref/out-libheif1.12-orient.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif
Exif:SubsecTimeOriginal: "006"
Exif:WhiteBalance: 0 (auto)
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/Chimera-AV1-8bit-162.avif
ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif
SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7
channel list: R, G, B
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/test-10bit.avif
ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: FFDB2271C871832C052DA19F0C0B18BB8AFC99EE
channel list: R, G, B, A
Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading cicp_pq.avif
cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: 973A58CD6B420F205AA89967C18A60CD8ED8894B
channel list: R, G, B, A
CICP: 9, 16, 9, 1
Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E
Expand Down
23 changes: 23 additions & 0 deletions testsuite/heif/ref/out-libheif1.4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif
SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7
channel list: R, G, B
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/test-10bit.avif
ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: FFDB2271C871832C052DA19F0C0B18BB8AFC99EE
channel list: R, G, B, A
Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading cicp_pq.avif
cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: 973A58CD6B420F205AA89967C18A60CD8ED8894B
channel list: R, G, B, A
CICP: 9, 16, 9, 1
Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E
Expand Down
23 changes: 23 additions & 0 deletions testsuite/heif/ref/out-libheif1.5.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif
SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7
channel list: R, G, B
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/test-10bit.avif
ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: FFDB2271C871832C052DA19F0C0B18BB8AFC99EE
channel list: R, G, B, A
Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading cicp_pq.avif
cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: 973A58CD6B420F205AA89967C18A60CD8ED8894B
channel list: R, G, B, A
CICP: 9, 16, 9, 1
Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E
Expand Down
28 changes: 28 additions & 0 deletions testsuite/heif/ref/out-libheif1.9-alt2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif
Exif:SubsecTimeOriginal: "006"
Exif:WhiteBalance: 0 (auto)
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/Chimera-AV1-8bit-162.avif
ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif
SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7
channel list: R, G, B
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/test-10bit.avif
ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: FFDB2271C871832C052DA19F0C0B18BB8AFC99EE
channel list: R, G, B, A
Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading cicp_pq.avif
cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: 973A58CD6B420F205AA89967C18A60CD8ED8894B
channel list: R, G, B, A
CICP: 9, 16, 9, 1
Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C
Expand Down
23 changes: 23 additions & 0 deletions testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif
SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7
channel list: R, G, B
oiio:ColorSpace: "srgb_rec709_scene"
Reading ref/test-10bit.avif
ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: FFDB2271C871832C052DA19F0C0B18BB8AFC99EE
channel list: R, G, B, A
Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading cicp_pq.avif
cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
SHA-1: 973A58CD6B420F205AA89967C18A60CD8ED8894B
channel list: R, G, B, A
CICP: 9, 16, 9, 1
Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A"
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png"
heif:UnassociatedAlpha: 1
oiio:BitsPerSample: 10
oiio:ColorSpace: "srgb_rec709_scene"
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C
Expand Down
Loading
Loading