Repository: sxyazi/yazi
File: yazi-adapter/src/image.rs (Rust)

Code before:

impl Image {
	pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> {
		let (mut img, orientation, icc) = 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 || {
			if img.width() > w || img.height() > h {
				img = img.resize(w, h, Self::filter());
			}
			if orientation != Orientation::NoTransforms {
				img.apply_orientation(orientation);
			}

			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)?;
			} else {
				let mut encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
				let width = img.width();
				let height = img.height();
				let mut data = img.into_rgb8().into_raw();
				Self::apply_profile(icc, &mut data)?;
				encoder.encode(&data, width, height, ExtendedColorType::Rgb8)?
			}

			Ok::<_, anyhow::Error>(buf)
		})
		.await??;

		Ok(Local::regular(&cache).write(buf).await?)
	}

	fn apply_profile(icc: Option<Vec<u8>>, data: &mut [u8]) -> Result<()> {
		// If ICC profile is present and it's not srgb, perform color transformation
		if let Some(ref icc_profile) = icc
			&& let Some(input) = Profile::new_from_slice(icc_profile, false)
			&& !input.is_sRGB()
		{
			let mut output = Profile::new_sRGB();
			output.precache_output_transform();

			let xfm = Transform::new(&input, &output, qcms::DataType::RGB8, qcms::Intent::default())
				.context("Failed creating the ICC transformer")?;
			xfm.apply(data);
		}
		Ok(())
	}

Review comment (performance):
I noticed that `apply_profile` only applied to the `precache` function, which means it will only work if the user doesn't disable the [`image` preloader](https://github.com/sxyazi/yazi/blob/2d55c9427dd67a359cd1e9908303bbf6156053d1/yazi-config/preset/yazi-default.toml#L124) and the image has [generated a cache](https://github.com/sxyazi/yazi/blob/2d55c9427dd67a359cd1e9908303bbf6156053d1/yazi-plugin/preset/plugins/image.lua#L23) at the time, which is problematic as image preview should behave the same way regardless of whether it has cached, i.e. it should also be implemented for [`downscale`](https://github.com/sxyazi/yazi/blob/2d55c9427dd67a359cd1e9908303bbf6156053d1/yazi-adapter/src/image.rs#L64) which is directlly called by [`image_show`](https://github.com/sxyazi/yazi/blob/2d55c9427dd67a359cd1e9908303bbf6156053d1/yazi-plugin/preset/plugins/image.lua#L11) (instead of `image_precache`), maybe this process should placed under [`decode_from`](https://github.com/sxyazi/yazi/blob/2d55c9427dd67a359cd1e9908303bbf6156053d1/yazi-adapter/src/image.rs#L118) which `precache` and `downscale` both call

Code after:
impl Image {
	pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> {
		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 || {
			if img.width() > w || img.height() > h {
				img = img.resize(w, h, Self::filter());
			}
			if orientation != Orientation::NoTransforms {
				img.apply_orientation(orientation);
			}

			let mut buf = Vec::new();
			if img.color().has_alpha() {
				let encoder = PngEncoder::new(&mut buf);
				img.write_with_encoder(encoder)?;
			} else {
				let encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
				img.write_with_encoder(encoder)?;
			}

			Ok::<_, ImageError>(buf)
		})
		.await??;

		Ok(Local::regular(&cache).write(buf).await?)
	}

	pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage> {
		let (mut img, orientation) = Self::decode_from(path).await?;
		let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));

		// Fast path.
		if img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {
			return Ok(img);
		}

		let img = tokio::task::spawn_blocking(move || {
			if img.width() > w || img.height() > h {
				img = img.resize(w, h, Self::filter())
			}
			if orientation != Orientation::NoTransforms {
				img.apply_orientation(orientation);
			}
			img
		})
		.await?;

		Ok(img)
	}