diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 1c3eb68967..7b0cca49c4 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -3066,6 +3066,11 @@ attributes are supported: - ptr - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for example by reading from memory rather than the file system. + * - ``oiio:UnassociatedAlpha`` + - int + - If nonzero, will leave alpha unassociated (versus the default of + premultiplying color channels by alpha if the alpha channel is + unassociated). **Configuration settings for WebP output** @@ -3088,6 +3093,12 @@ control aspects of the writing itself: - ptr - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for example by writing to a memory buffer. + * - ``oiio:UnassociatedAlpha`` + - int + - If nonzero, indicates that the data being passed is already in + unassociated form (non-premultiplied colors) and should stay that way + for output rather than being assumed to be associated and get automatic + un-association to store in the file. **Custom I/O Overrides** diff --git a/src/webp.imageio/webpinput.cpp b/src/webp.imageio/webpinput.cpp index fe93c6101d..37b88b4fd7 100644 --- a/src/webp.imageio/webpinput.cpp +++ b/src/webp.imageio/webpinput.cpp @@ -44,8 +44,9 @@ class WebpInput final : public ImageInput { int m_frame_count = 1; WebPDemuxer* m_demux = nullptr; WebPIterator m_iter; - int m_subimage = -1; // Subimage we're pointed to - int m_subimage_read = -1; // Subimage stored in decoded_image + int m_subimage = -1; // Subimage we're pointed to + int m_subimage_read = -1; // Subimage stored in decoded_image + bool m_keep_unassociated_alpha = false; // Do not convert unassociated alpha void init(void) { @@ -204,6 +205,9 @@ WebpInput::open(const std::string& name, ImageSpec& spec, // Make space for the decoded image m_decoded_image.reset(new uint8_t[m_spec.image_bytes()]); + if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) + m_keep_unassociated_alpha = true; + seek_subimage(0, 0); spec = m_spec; return true; @@ -306,12 +310,15 @@ WebpInput::read_current_subimage() m_decoded_image.get() + offset, m_spec.image_bytes() - offset, m_spec.scanline_bytes()); - // WebP requires unassociated alpha, and it's sRGB. - // Handle this all by wrapping an IB around it. - ImageBuf fullbuf(m_spec, - span((std::byte*)m_decoded_image.get(), - m_spec.image_bytes())); - ImageBufAlgo::premult(fullbuf, fullbuf); + + // WebP is unassociated alpha and sRGB. + // Convert to the OIIO-native associated form if required. + if (!m_keep_unassociated_alpha) { + ImageBuf fullbuf( + m_spec, span((std::byte*)m_decoded_image.get(), + m_spec.image_bytes())); + ImageBufAlgo::premult(fullbuf, fullbuf); + } } } else { // This subimage writes *atop* the prior image, we must composite @@ -328,10 +335,14 @@ WebpInput::read_current_subimage() (uint8_t*)fragbuf.localpixels(), fragspec.image_bytes(), fragspec.scanline_bytes()); - // WebP requires unassociated alpha, and it's sRGB. - // Handle this all by wrapping an IB around it. - ImageBufAlgo::premult(fragbuf, fragbuf); - ImageBufAlgo::over(fullbuf, fragbuf, fullbuf); + + + // WebP is unassociated alpha and sRGB. + // Convert to the OIIO-native associated form if required. + if (!m_keep_unassociated_alpha) { + ImageBufAlgo::premult(fragbuf, fragbuf); + ImageBufAlgo::over(fullbuf, fragbuf, fullbuf); + } } if (!okptr) { @@ -371,7 +382,8 @@ WebpInput::close() } m_decoded_image.reset(); m_encoded_image.reset(); - m_subimage = -1; + m_subimage = -1; + m_keep_unassociated_alpha = false; init(); return true; } diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 44ee385aab..178e2d407b 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -36,6 +36,7 @@ class WebpOutput final : public ImageOutput { std::string m_filename; imagesize_t m_scanline_size; unsigned int m_dither; + bool m_convert_alpha; // Do we deassociate alpha? std::vector m_uncompressed_image; void init() @@ -113,7 +114,9 @@ WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) // forcing UINT8 format m_spec.set_format(TypeDesc::UINT8); - m_dither = m_spec.get_int_attribute("oiio:dither", 0); + m_dither = m_spec.get_int_attribute("oiio:dither", 0); + m_convert_alpha = m_spec.alpha_channel != -1 + && !m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0); m_scanline_size = m_spec.scanline_bytes(); m_uncompressed_image.resize(m_spec.image_bytes(), 0); @@ -136,20 +139,25 @@ WebpOutput::write_scanline(int y, int z, TypeDesc format, const void* data, if (y == m_spec.height - 1) { if (m_spec.nchannels == 4) { - // WebP requires unassociated alpha, and it's sRGB. - // Handle this all by wrapping an IB around it. - ImageSpec specwrap(m_spec.width, m_spec.height, 4, TypeUInt8); - ImageBuf bufwrap(specwrap, cspan(m_uncompressed_image)); - ROI rgbroi(0, m_spec.width, 0, m_spec.height, 0, 1, 0, 3); - ImageBufAlgo::pow(bufwrap, bufwrap, 2.2f, rgbroi); - ImageBufAlgo::unpremult(bufwrap, bufwrap); - ImageBufAlgo::pow(bufwrap, bufwrap, 1.0f / 2.2f, rgbroi); + if (m_convert_alpha) { + // WebP requires unassociated alpha, and it's sRGB. + // Handle this all by wrapping an IB around it. + ImageSpec specwrap(m_spec.width, m_spec.height, 4, TypeUInt8); + ImageBuf bufwrap(specwrap, + cspan(m_uncompressed_image)); + ROI rgbroi(0, m_spec.width, 0, m_spec.height, 0, 1, 0, 3); + ImageBufAlgo::pow(bufwrap, bufwrap, 2.2f, rgbroi); + ImageBufAlgo::unpremult(bufwrap, bufwrap); + ImageBufAlgo::pow(bufwrap, bufwrap, 1.0f / 2.2f, rgbroi); + } + WebPPictureImportRGBA(&m_webp_picture, m_uncompressed_image.data(), m_scanline_size); } else { WebPPictureImportRGB(&m_webp_picture, m_uncompressed_image.data(), m_scanline_size); } + if (!WebPEncode(&m_webp_config, &m_webp_picture)) { errorfmt("Failed to encode {} as WebP image", m_filename); close();