Skip to content

Commit c3cf7b9

Browse files
authored
heif: Handle unassociated alpha (#3146)
Turns out that heif files that contain alpha can have either associated or unassociated alpha. We had been assuming associated. With LibHeif 1.12 and beyond, you can ask, so now we do. Thanks Frédéric Devernay for pointing this out. Similar to other formats that optionally have unassociated alpha: * The default behavior is to associate/premultiply automatically when reading, but set metadata "heif:UnassociatedAlpha" to indicate that it was unassociated in the file. * Open configuration hint "oiio:UnassociatedAlpha", when nonzero, means to please keep the data unassociated and not premultiply. In that case, metadata "oiio:UnassociatedAlpha" will be set in the ImageSpec to indicate that the returned image (not just the file) consists of unassociated values. I did a couple other minor docs touch-ups while I was at it. Fixes #3129
1 parent 6507724 commit c3cf7b9

File tree

3 files changed

+92
-13
lines changed

3 files changed

+92
-13
lines changed

INSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**.
5454
* giflib >= 4.1 (tested through 5.2; 5.0+ is strongly recommended for
5555
stability and thread safety)
5656
* If you want support for HEIF/HEIC or AVIF images:
57-
* libheif >= 1.3 (1.7 required for AVIF support, tested through 1.11)
57+
* libheif >= 1.3 (1.7 required for AVIF support, tested through 1.12)
5858
* libheif must be built with an AV1 encoder/decoder for AVIF support.
5959
* Avoid libheif 1.10 on Mac, it is very broken. Libheif 1.11 is fine.
6060
* If you want support for DDS files:

src/doc/builtinplugins.rst

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -639,12 +639,31 @@ currently supported for reading, but not yet writing. All pixel data is
639639
uint8, though we hope to add support for HDR (more than 8 bits) in the
640640
future.
641641

642+
**Configuration settings for HEIF input**
643+
644+
When opening an HEIF ImageInput with a *configuration* (see
645+
Section :ref:`sec-inputwithconfig`), the following special configuration
646+
attributes are supported:
647+
648+
.. list-table::
649+
:widths: 30 10 65
650+
:header-rows: 1
651+
652+
* - Input Configuration Attribute
653+
- Type
654+
- Meaning
655+
* - ``oiio:UnassociatedAlpha``
656+
- int
657+
- If nonzero, and the file contains unassociated alpha, this will
658+
cause the reader to leave alpha unassociated (versus the default of
659+
premultiplying color channels by alpha if the alpha channel is
660+
unassociated).
661+
642662
**Configuration settings for HEIF output**
643663

644664
When opening an HEIF ImageOutput, the following special metadata tokens
645665
control aspects of the writing itself:
646666

647-
648667
.. list-table::
649668
:widths: 30 10 65
650669
:header-rows: 1
@@ -1896,7 +1915,8 @@ options are supported:
18961915
- Meaning
18971916
* - ``oiio:UnassociatedAlpha``
18981917
- int
1899-
- If nonzero, will leave alpha unassociated (versus the default of
1918+
- If nonzero, and the file contains unassociated alpha, this will
1919+
cause the reader to leave alpha unassociated (versus the default of
19001920
premultiplying color channels by alpha if the alpha channel is
19011921
unassociated).
19021922
* - ``oiio:RawColor``
@@ -2128,10 +2148,12 @@ aware of:
21282148
- The actual bits per sample in the file (may differ from `ImageSpec::format`).
21292149
* - ``oiio:UnassociatedAlpha``
21302150
- int
2131-
- Nonzero if the alpha channel contained "unassociated" alpha.
2132-
2133-
2134-
2151+
- Nonzero if the data returned by OIIO will have "unassociated" alpha.
2152+
* - ``tiff:UnassociatedAlpha``
2153+
- int
2154+
- Nonzero if the data in the file had "unassociated" alpha (even if using
2155+
the usual convention of returning associated alpha from the read
2156+
methods).
21352157

21362158

21372159

src/heif.imageio/heifinput.cpp

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ class HeifInput final : public ImageInput {
3939
virtual bool seek_subimage(int subimage, int miplevel) override;
4040
virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
4141
void* data) override;
42+
virtual bool read_scanline(int y, int z, TypeDesc format, void* data,
43+
stride_t xstride) override;
4244

4345
private:
4446
std::string m_filename;
45-
int m_subimage = -1;
46-
int m_num_subimages = 0;
47-
int m_has_alpha = false;
47+
int m_subimage = -1;
48+
int m_num_subimages = 0;
49+
int m_has_alpha = false;
50+
bool m_associated_alpha = true;
51+
bool m_keep_unassociated_alpha = false;
52+
bool m_do_associate = false;
4853
std::unique_ptr<heif::Context> m_ctx;
4954
heif_item_id m_primary_id; // id of primary image
5055
std::vector<heif_item_id> m_item_ids; // ids of all other images
@@ -108,7 +113,7 @@ HeifInput::open(const std::string& name, ImageSpec& newspec)
108113

109114
bool
110115
HeifInput::open(const std::string& name, ImageSpec& newspec,
111-
const ImageSpec& /*config*/)
116+
const ImageSpec& config)
112117
{
113118
m_filename = name;
114119
m_subimage = -1;
@@ -117,6 +122,9 @@ HeifInput::open(const std::string& name, ImageSpec& newspec,
117122
m_himage = heif::Image();
118123
m_ihandle = heif::ImageHandle();
119124

125+
m_keep_unassociated_alpha
126+
= (config.get_int_attribute("oiio:UnassociatedAlpha") != 0);
127+
120128
try {
121129
m_ctx->read_from_file(name);
122130
// FIXME: should someday be read_from_reader to give full flexibility
@@ -155,8 +163,11 @@ HeifInput::close()
155163
m_himage = heif::Image();
156164
m_ihandle = heif::ImageHandle();
157165
m_ctx.reset();
158-
m_subimage = -1;
159-
m_num_subimages = 0;
166+
m_subimage = -1;
167+
m_num_subimages = 0;
168+
m_associated_alpha = true;
169+
m_keep_unassociated_alpha = false;
170+
m_do_associate = false;
160171
return true;
161172
}
162173

@@ -200,6 +211,27 @@ HeifInput::seek_subimage(int subimage, int miplevel)
200211

201212
m_spec.attribute("oiio:ColorSpace", "sRGB");
202213

214+
#if LIBHEIF_HAVE_VERSION(1, 12, 0)
215+
// Libheif >= 1.12 added API call to find out if the image is associated
216+
// alpha (i.e. colors are premultiplied).
217+
m_associated_alpha = m_himage.is_premultiplied_alpha();
218+
m_do_associate = (!m_associated_alpha && m_spec.alpha_channel >= 0
219+
&& !m_keep_unassociated_alpha);
220+
if (!m_associated_alpha && m_spec.nchannels >= 4) {
221+
// Indicate that file stored unassociated alpha data
222+
m_spec.attribute("heif:UnassociatedAlpha", 1);
223+
// If we don't have 4 chans, we need not consider
224+
m_keep_unassociated_alpha &= (m_spec.nchannels >= 4);
225+
if (m_keep_unassociated_alpha) {
226+
// Indicate that we are returning unassociated data if the file
227+
// had associated and we were asked to keep it that way.
228+
m_spec.attribute("oiio:UnassociatedAlpha", 1);
229+
}
230+
}
231+
#else
232+
m_associated_alpha = true; // assume/hope
233+
#endif
234+
203235
auto meta_ids = m_ihandle.get_list_of_metadata_block_IDs();
204236
// std::cout << "nmeta? " << meta_ids.size() << "\n";
205237
for (auto m : meta_ids) {
@@ -270,4 +302,29 @@ HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
270302
}
271303

272304

305+
306+
bool
307+
HeifInput::read_scanline(int y, int z, TypeDesc format, void* data,
308+
stride_t xstride)
309+
{
310+
bool ok = ImageInput::read_scanline(y, z, format, data, xstride);
311+
if (ok && m_do_associate) {
312+
// If alpha is unassociated and we aren't requested to keep it that
313+
// way, multiply the colors by alpha per the usual OIIO conventions
314+
// to deliver associated color & alpha. Any auto-premultiplication
315+
// by alpha should happen after we've already done data format
316+
// conversions. That's why we do it here, rather than in
317+
// read_native_blah.
318+
{
319+
lock_guard lock(*this);
320+
if (format == TypeUnknown) // unknown -> retrieve native type
321+
format = m_spec.format;
322+
}
323+
OIIO::premult(m_spec.nchannels, m_spec.width, 1, 1, 0 /*chbegin*/,
324+
m_spec.nchannels /*chend*/, format, data, xstride,
325+
AutoStride, AutoStride, m_spec.alpha_channel);
326+
}
327+
return ok;
328+
}
329+
273330
OIIO_PLUGIN_NAMESPACE_END

0 commit comments

Comments
 (0)