mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
Support 8-bit images in RGB, CIELAB, and GRAY color spaces (#3358)
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
0000141700
commit
a6fcbb04df
8 changed files with 146 additions and 33 deletions
1
.github/workflows/draft.yml
vendored
1
.github/workflows/draft.yml
vendored
|
|
@ -230,6 +230,7 @@ jobs:
|
|||
echo 'NIGHTLY_BODY<<EOF' >> $GITHUB_ENV
|
||||
echo "From commit: ${GITHUB_SHA:0:8}" >> $GITHUB_ENV
|
||||
echo "Generated on: $(date -u +"%Y-%m-%d %H:%M") UTC" >> $GITHUB_ENV
|
||||
echo "Nightly changelog: https://github.com/sxyazi/yazi/blob/main/CHANGELOG.md#unreleased" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Added
|
||||
|
||||
- Support VFS for preset previewers that rely on external commands ([#3477])
|
||||
- Support 8-bit images in RGB, CIELAB, and GRAY color spaces ([#3358])
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -1559,6 +1560,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3290]: https://github.com/sxyazi/yazi/pull/3290
|
||||
[#3313]: https://github.com/sxyazi/yazi/pull/3313
|
||||
[#3317]: https://github.com/sxyazi/yazi/pull/3317
|
||||
[#3358]: https://github.com/sxyazi/yazi/pull/3358
|
||||
[#3360]: https://github.com/sxyazi/yazi/pull/3360
|
||||
[#3361]: https://github.com/sxyazi/yazi/pull/3361
|
||||
[#3364]: https://github.com/sxyazi/yazi/pull/3364
|
||||
|
|
|
|||
77
Cargo.lock
generated
77
Cargo.lock
generated
|
|
@ -834,7 +834,7 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
"signal-hook 0.3.18",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
|
@ -856,7 +856,7 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"rustix 1.1.3",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
"signal-hook 0.3.18",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
|
@ -983,8 +983,18 @@ version = "0.20.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.20.11",
|
||||
"darling_macro 0.20.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"darling_macro 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1001,13 +1011,37 @@ dependencies = [
|
|||
"syn 2.0.112",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||
dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.112",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.20.11",
|
||||
"quote",
|
||||
"syn 2.0.112",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"quote",
|
||||
"syn 2.0.112",
|
||||
]
|
||||
|
|
@ -1102,7 +1136,7 @@ version = "0.20.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.11",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.112",
|
||||
|
|
@ -1915,11 +1949,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c"
|
||||
checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.23.0",
|
||||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -3918,6 +3952,16 @@ dependencies = [
|
|||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.5"
|
||||
|
|
@ -3926,7 +3970,7 @@ checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
"signal-hook 0.3.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3941,13 +3985,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "signal-hook-tokio"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
|
||||
checksum = "e513e435a8898a0002270f29d0a708b7879708fb5c4d00e46983ca2d2d378cf0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"libc",
|
||||
"signal-hook",
|
||||
"signal-hook 0.4.1",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
@ -4227,7 +4271,7 @@ dependencies = [
|
|||
"phf",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"signal-hook",
|
||||
"signal-hook 0.3.18",
|
||||
"siphasher",
|
||||
"terminfo",
|
||||
"termios",
|
||||
|
|
@ -5360,6 +5404,7 @@ dependencies = [
|
|||
"base64",
|
||||
"crossterm 0.29.0",
|
||||
"image",
|
||||
"moxcms",
|
||||
"palette",
|
||||
"quantette",
|
||||
"ratatui",
|
||||
|
|
@ -5888,9 +5933,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f"
|
||||
checksum = "de9211a9f64b825911bdf0240f58b7a8dac217fe260fc61f080a07f61372fbd5"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ anyhow = { workspace = true }
|
|||
base64 = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
image = { version = "0.25.9", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
moxcms = "0.7.11"
|
||||
palette = { version = "0.7.6", default-features = false }
|
||||
quantette = { version = "0.5.1", default-features = false }
|
||||
ratatui = { workspace = true }
|
||||
|
|
|
|||
69
yazi-adapter/src/icc.rs
Normal file
69
yazi-adapter/src/icc.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use anyhow::Context;
|
||||
use image::{ColorType, DynamicImage, GrayAlphaImage, GrayImage, ImageDecoder, RgbImage, RgbaImage, metadata::Cicp};
|
||||
use moxcms::{CicpColorPrimaries, ColorProfile, DataColorSpace, Layout, TransferCharacteristics, TransformOptions};
|
||||
|
||||
pub(super) struct Icc;
|
||||
impl Icc {
|
||||
pub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result<DynamicImage> {
|
||||
if let Some(layout) = Self::color_type_to_layout(decoder.color_type())
|
||||
&& let Some(icc) = decoder.icc_profile().unwrap_or_default()
|
||||
&& let Ok(profile) = ColorProfile::new_from_slice(&icc)
|
||||
&& Self::requires_transform(&profile)
|
||||
{
|
||||
let mut buf = vec![0u8; decoder.total_bytes() as usize];
|
||||
let (w, h) = decoder.dimensions();
|
||||
decoder.read_image(&mut buf)?;
|
||||
|
||||
let transformer = profile
|
||||
// TODO: Use `create_transform_in_place_nbit` in the next minor version of moxcms.
|
||||
.create_transform_8bit(layout, &ColorProfile::new_srgb(), layout, TransformOptions::default())
|
||||
.context("cannot make a profile transformer")?;
|
||||
|
||||
let mut converted = vec![0u8; buf.len()];
|
||||
transformer.transform(&buf, &mut converted).context("cannot transform image")?;
|
||||
|
||||
let mut image: DynamicImage = match layout {
|
||||
Layout::Gray => {
|
||||
GrayImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
|
||||
}
|
||||
Layout::GrayAlpha => {
|
||||
GrayAlphaImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
|
||||
}
|
||||
Layout::Rgb => {
|
||||
RgbImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
|
||||
}
|
||||
Layout::Rgba => {
|
||||
RgbaImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
image.set_rgb_primaries(Cicp::SRGB.primaries);
|
||||
image.set_transfer_function(Cicp::SRGB.transfer);
|
||||
Ok(image)
|
||||
} else {
|
||||
Ok(DynamicImage::from_decoder(decoder)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn color_type_to_layout(color_type: ColorType) -> Option<Layout> {
|
||||
match color_type {
|
||||
ColorType::L8 => Some(Layout::Gray),
|
||||
ColorType::La8 => Some(Layout::GrayAlpha),
|
||||
ColorType::Rgb8 => Some(Layout::Rgb),
|
||||
ColorType::Rgba8 => Some(Layout::Rgba),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn requires_transform(profile: &ColorProfile) -> bool {
|
||||
if profile.color_space == DataColorSpace::Cmyk {
|
||||
return false;
|
||||
}
|
||||
|
||||
profile.cicp.is_none_or(|c| {
|
||||
c.color_primaries != CicpColorPrimaries::Bt709
|
||||
|| c.transfer_characteristics != TransferCharacteristics::Srgb
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
|
||||
use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
|
||||
use crate::Dimension;
|
||||
use crate::{Dimension, Icc};
|
||||
|
||||
pub struct Image;
|
||||
|
||||
impl Image {
|
||||
pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> {
|
||||
let (mut img, orientation, icc) = Self::decode_from(src).await?;
|
||||
let (mut img, orientation) = Self::decode_from(src).await?;
|
||||
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
|
||||
|
||||
let buf = tokio::task::spawn_blocking(move || {
|
||||
|
|
@ -25,14 +25,11 @@ impl Image {
|
|||
|
||||
let mut buf = Vec::new();
|
||||
if img.color().has_alpha() {
|
||||
let rgba = img.into_rgba8();
|
||||
let mut encoder = PngEncoder::new(&mut buf);
|
||||
icc.map(|b| encoder.set_icc_profile(b));
|
||||
encoder.write_image(&rgba, rgba.width(), rgba.height(), ExtendedColorType::Rgba8)?;
|
||||
let encoder = PngEncoder::new(&mut buf);
|
||||
img.write_with_encoder(encoder)?;
|
||||
} else {
|
||||
let mut encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
|
||||
icc.map(|b| encoder.set_icc_profile(b));
|
||||
encoder.encode_image(&img.into_rgb8())?;
|
||||
let encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
|
||||
img.write_with_encoder(encoder)?;
|
||||
}
|
||||
|
||||
Ok::<_, ImageError>(buf)
|
||||
|
|
@ -43,7 +40,7 @@ impl Image {
|
|||
}
|
||||
|
||||
pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage> {
|
||||
let (mut img, orientation, _) = Self::decode_from(path).await?;
|
||||
let (mut img, orientation) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
|
||||
|
||||
// Fast path.
|
||||
|
|
@ -96,7 +93,7 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
async fn decode_from(path: PathBuf) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
|
||||
async fn decode_from(path: PathBuf) -> Result<(DynamicImage, Orientation)> {
|
||||
let mut limits = Limits::no_limits();
|
||||
if YAZI.tasks.image_alloc > 0 {
|
||||
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
|
||||
|
|
@ -114,9 +111,7 @@ impl Image {
|
|||
|
||||
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
|
||||
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
let icc = decoder.icc_profile().unwrap_or_default();
|
||||
|
||||
Ok((DynamicImage::from_decoder(decoder)?, orientation, icc))
|
||||
Ok((Icc::transform(decoder)?, orientation))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
yazi_macro::mod_pub!(drivers);
|
||||
|
||||
yazi_macro::mod_flat!(adapter brand dimension emulator image info mux unknown);
|
||||
yazi_macro::mod_flat!(adapter brand dimension emulator icc image info mux unknown);
|
||||
|
||||
use yazi_shared::{RoCell, SyncCell, in_wsl};
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ tracing-subscriber = { version = "0.3.22", features = [ "env-filter" ] }
|
|||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
|
||||
signal-hook-tokio = { version = "0.4.0", features = [ "futures-v0_3" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue