webp icon indicating copy to clipboard operation
webp copied to clipboard

ICC Profile

Open neckaros opened this issue 1 year ago • 3 comments

Is there a way like for Encoder of the Image crate to embed an ICC profile in the encoder?

    /// Set the ICC profile to use for the image.
    ///
    /// This function is a no-op for formats that don't support ICC profiles.
    /// For formats that do support ICC profiles, the profile will be embedded
    /// in the image when it is saved.
    ///
    /// # Errors
    ///
    /// This function returns an error if the format does not support ICC profiles.
    fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
        let _ = icc_profile;
        Err(UnsupportedError::from_format_and_kind(
            ImageFormatHint::Unknown,
            UnsupportedErrorKind::GenericFeature(
                "ICC profiles are not supported for this format".into(),
            ),
        ))
    }

WebP Specification: https://developers.google.com/speed/webp/docs/riff_container

Color Profile: Chunk Size bytes ICC profile. This chunk MUST appear before the image data.

There SHOULD be at most one such chunk. If there are more such chunks, readers MAY ignore all except the first one. See the ICC Specification for details.

If this chunk is not present, sRGB SHOULD be assumed.

neckaros avatar Jan 24 '25 16:01 neckaros

I am also very interested in this. I would like to use this crate to convert images from JPEG and PNG to WebP, but due to the lack of support for embedding an ICC profile into the generated WebP file I cannot do that without major color distortion for some images.

Shnatsel avatar Aug 30 '25 21:08 Shnatsel

We are interested as well.

fd avatar Sep 04 '25 13:09 fd

We are also missing this feature.

As a workaround, we solved it by adding libwebp-sys2 = {version = "0.2.0", features = ["mux"]} to our dependencies, as well as this function:

/// Adds the given ICC profile to the given WebP data using libwebp's mux functionality.
/// Returns `Some<Vec<u8>>` containing the new WebP data with the ICC profile if successful, otherwise `None`.
fn mux_icc_profile(webp_data: &[u8], icc_profile: &[u8]) -> Option<Vec<u8>> {
    unsafe {
        // Wrap WebP data
        let webp_data_struct = libwebp_sys::WebPData {
            bytes: webp_data.as_ptr(),
            size: webp_data.len(),
        };

        // Create mux
        let mux = libwebp_sys::WebPMuxNew();
        if mux.is_null() {
            return None;
        }

        // Set image data
        if libwebp_sys::WebPMuxSetImage(mux, &webp_data_struct, 1) != libwebp_sys::WEBP_MUX_OK {
            libwebp_sys::WebPMuxDelete(mux);
            return None;
        }

        // Add ICC profile chunk
        let icc_data = libwebp_sys::WebPData {
            bytes: icc_profile.as_ptr(),
            size: icc_profile.len(),
        };
        if libwebp_sys::WebPMuxSetChunk(mux, c"ICCP".as_ptr(), &icc_data, 1) != libwebp_sys::WEBP_MUX_OK {
            libwebp_sys::WebPMuxDelete(mux);
            return None;
        }

        // Assemble final WebP
        let mut output = libwebp_sys::WebPData { bytes: std::ptr::null(), size: 0 };
        if libwebp_sys::WebPMuxAssemble(mux, &mut output) != libwebp_sys::WEBP_MUX_OK {
            libwebp_sys::WebPMuxDelete(mux);
            return None;
        }

        // Copy to Vec<u8>
        let result = std::slice::from_raw_parts(output.bytes, output.size).to_vec();

        // Free
        libwebp_sys::WebPDataClear(&mut output);
        libwebp_sys::WebPMuxDelete(mux);

        Some(result)
    }
}

misl-smlz avatar Sep 25 '25 10:09 misl-smlz