decoder: Add basic color profile support

This only covers 8bit RGB(A) JPEG, PNG, and HEIF.
Should only be a temporary solution until GTK supports
color profiles.

Closes #36

Part-of: <https://gitlab.gnome.org/Incubator/loupe/-/merge_requests/172>
This commit is contained in:
Sophie Herold 2023-04-12 04:13:30 +02:00
parent 3b6cc8c404
commit e710419a22
6 changed files with 186 additions and 23 deletions

View file

@ -2,14 +2,16 @@
use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::util;
use crate::util::gettext::*;
use crate::util::ToBufRead;
use anyhow::Context;
use arc_swap::ArcSwap;
use gtk::prelude::*;
use libheif_rs::{ColorSpace, HeifContext, LibHeif, Plane, RgbChroma};
use libheif_rs::{ColorProfile, ColorSpace, HeifContext, LibHeif, Plane, RgbChroma};
use once_cell::sync::Lazy;
use rgb::AsPixels;
use std::sync::Arc;
@ -73,6 +75,21 @@ impl Heif {
.decode(&handle, ColorSpace::Rgb(rgb_chroma), None)
.context(gettext("Failed to decode image"))?;
let icc_profile = if let Some(profile) = handle.color_profile_raw() {
if [
libheif_rs::color_profile_types::R_ICC,
libheif_rs::color_profile_types::PROF,
]
.contains(&profile.profile_type())
{
Some(profile.data)
} else {
None
}
} else {
None
};
let plane = image
.planes_mut()
.interleaved
@ -83,6 +100,7 @@ impl Heif {
has_alpha_channel: handle.has_alpha_channel(),
pre_multiplied_alpha: handle.is_premultiplied_alpha(),
rgb_chroma,
icc_profile,
};
let tile = tiling::Tile {
@ -106,6 +124,7 @@ pub struct Decoded<'a> {
has_alpha_channel: bool,
pre_multiplied_alpha: bool,
rgb_chroma: RgbChroma,
icc_profile: Option<Vec<u8>>,
}
impl<'a> Decoded<'a> {
@ -150,7 +169,19 @@ impl<'a> Decoded<'a> {
RgbChroma::C444 => unreachable!(),
};
let bytes = glib::Bytes::from_owned(self.plane.data.to_vec());
let mut buffer = self.plane.data.to_vec();
if let Some(icc_profile) = &self.icc_profile {
if memory_format == gdk::MemoryFormat::R8g8b8 {
util::new_trafo::<rgb::RGB8>(icc_profile, lcms2::PixelFormat::RGB_8)?
.transform_in_place(buffer.as_pixels_mut());
} else if memory_format == gdk::MemoryFormat::R8g8b8a8 {
util::new_trafo::<rgb::RGBA8>(icc_profile, lcms2::PixelFormat::RGBA_8)?
.transform_in_place(buffer.as_pixels_mut());
}
}
let bytes = glib::Bytes::from_owned(buffer);
let tex = gdk::MemoryTexture::new(
self.plane.width as i32,

View file

@ -2,13 +2,15 @@
use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::util;
use crate::util::gettext::*;
use crate::util::{BufReadSeek, ToBufRead};
use anyhow::{bail, Context};
use arc_swap::ArcSwap;
use gtk::prelude::*;
use image_rs::AnimationDecoder;
use image_rs::{AnimationDecoder, ImageDecoder};
use rgb::AsPixels;
use std::sync::Arc;
@ -87,13 +89,38 @@ impl ImageRsOther {
tiles.set_original_dimensions(dimensions);
let mut reader = Self::reader(&file, format)?;
let (icc_profile, dynamic_image) = match format {
image_rs::ImageFormat::Jpeg => {
let reader = file.to_buf_read()?;
let mut decoder = image_rs::codecs::jpeg::JpegDecoder::new(reader)?;
let _ = decoder.set_limits(image_rs::io::Limits::no_limits());
let icc_profile = decoder.icc_profile();
let dynamic_image = image_rs::DynamicImage::from_decoder(decoder)
.context(gettext("Failed to decode image"))?;
(icc_profile, dynamic_image)
}
image_rs::ImageFormat::Png => {
let reader = file.to_buf_read()?;
let mut decoder = image_rs::codecs::png::PngDecoder::new(reader)?;
let _ = decoder.set_limits(image_rs::io::Limits::no_limits());
let icc_profile = decoder.icc_profile();
let dynamic_image = image_rs::DynamicImage::from_decoder(decoder)
.context(gettext("Failed to decode image"))?;
(icc_profile, dynamic_image)
}
_ => {
let mut reader = Self::reader(&file, format)?;
reader.limits(image_rs::io::Limits::no_limits());
let dynamic_image =
reader.decode().context(gettext("Failed to decode image"))?;
(None, dynamic_image)
}
};
reader.limits(image_rs::io::Limits::no_limits());
let dynamic_image = reader.decode().context(gettext("Failed to decode image"))?;
let decoded_image = Decoded { dynamic_image };
let decoded_image = Decoded {
dynamic_image,
icc_profile,
};
let tile = tiling::Tile {
position: (0, 0),
@ -177,7 +204,10 @@ impl ImageRsOther {
let dynamic_image = image_rs::DynamicImage::from(rgb_image);
let decoded_image = Decoded { dynamic_image };
let decoded_image = Decoded {
dynamic_image,
icc_profile: None,
};
let tile = tiling::Tile {
position,
@ -224,6 +254,7 @@ impl ImageRsOther {
pub struct Decoded {
dynamic_image: image_rs::DynamicImage,
icc_profile: Option<Vec<u8>>,
}
impl Decoded {
@ -247,18 +278,30 @@ impl Decoded {
buffer.into_raw(),
)
}
image_rs::DynamicImage::ImageRgb8(buffer) => (
1,
gdk::MemoryFormat::R8g8b8,
buffer.sample_layout(),
buffer.into_raw(),
),
image_rs::DynamicImage::ImageRgba8(buffer) => (
1,
gdk::MemoryFormat::R8g8b8a8,
buffer.sample_layout(),
buffer.into_raw(),
),
image_rs::DynamicImage::ImageRgb8(mut buffer) => {
if let Some(icc_profile) = &self.icc_profile {
util::new_trafo::<rgb::RGB8>(icc_profile, lcms2::PixelFormat::RGB_8)?
.transform_in_place(buffer.as_pixels_mut());
}
(
1,
gdk::MemoryFormat::R8g8b8,
buffer.sample_layout(),
buffer.into_raw(),
)
}
image_rs::DynamicImage::ImageRgba8(mut buffer) => {
if let Some(icc_profile) = &self.icc_profile {
util::new_trafo::<rgb::RGBA8>(icc_profile, lcms2::PixelFormat::RGBA_8)?
.transform_in_place(buffer.as_pixels_mut());
}
(
1,
gdk::MemoryFormat::R8g8b8a8,
buffer.sample_layout(),
buffer.into_raw(),
)
}
img @ image_rs::DynamicImage::ImageLuma16(_) => {
let buffer = img.to_rgb16();
(