mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
plan(01): create phase 1 implementation plan
This commit is contained in:
parent
96f443baa0
commit
6a199a81c6
2 changed files with 476 additions and 0 deletions
59
.planning/ROADMAP.md
Normal file
59
.planning/ROADMAP.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Roadmap: Yazi Image Preview Quality Improvement
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Two phases deliver a surgical, single-file change to `yazi-adapter/src/image.rs`. Phase 1
|
||||||
|
implements linear-light-correct downscaling using `fast_image_resize`, covering all resize and
|
||||||
|
compatibility requirements in one coherent pass (the three correctness invariants — gamma
|
||||||
|
linearization, alpha premultiplication, filter config mapping — cannot be split without creating
|
||||||
|
partial-correctness windows). Phase 2 adds the unit test for linear-light correctness and
|
||||||
|
validates the implementation against the full image-type smoke test matrix.
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
**Phase Numbering:**
|
||||||
|
- Integer phases (1, 2, 3): Planned milestone work
|
||||||
|
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
||||||
|
|
||||||
|
Decimal phases appear between their surrounding integers in numeric order.
|
||||||
|
|
||||||
|
- [ ] **Phase 1: Implementation** - Replace both resize call sites with linear-light-correct `fast_image_resize`, add dependency, map all config filter strings
|
||||||
|
- [ ] **Phase 2: Verification** - Add unit test for linear-light correctness and smoke-test across all image types and terminal protocols
|
||||||
|
|
||||||
|
## Phase Details
|
||||||
|
|
||||||
|
### Phase 1: Implementation
|
||||||
|
**Goal**: Image previews use linear-light-correct downscaling via `fast_image_resize` with all existing config options preserved
|
||||||
|
**Depends on**: Nothing (first phase)
|
||||||
|
**Requirements**: RESIZE-01, RESIZE-02, RESIZE-03, RESIZE-04, RESIZE-05, COMPAT-01, COMPAT-02, COMPAT-03, COMPAT-04
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. Opening an image in yazi produces a visibly sharper preview than before (no blurring of fine detail at default lanczos3 filter)
|
||||||
|
2. All existing `image_filter` config values (`nearest`, `triangle`, `catmull-rom`, `gaussian`, `lanczos3`) continue to work without error or silent degradation
|
||||||
|
3. All terminal protocol drivers (KGP, KGP-old, IIP, Sixel, Chafa, Ueberzug) display images without regressions
|
||||||
|
4. `precache()` public signature is unchanged — Lua plugins that call it continue to function
|
||||||
|
5. `cargo build --release` succeeds and `cargo clippy --all` produces no new warnings
|
||||||
|
**Plans:** 1 plan
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
- [ ] 01-01-PLAN.md — Replace resize pipeline with linear-light fast_image_resize (dependency + code + verification)
|
||||||
|
|
||||||
|
### Phase 2: Verification
|
||||||
|
**Goal**: The implementation is confirmed correct across all image types, protocols, and edge cases, with at least one automated correctness test
|
||||||
|
**Depends on**: Phase 1
|
||||||
|
**Requirements**: TEST-01
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. A unit test exists that downscales a 50% gray sRGB patch to 1x1 and asserts the result is within tolerance of the linear-light-correct value (not the sRGB-space-incorrect midpoint)
|
||||||
|
2. RGBA PNG with transparency displays without color fringing on transparent edges in IIP/WezTerm
|
||||||
|
3. EXIF-rotated JPEG (phone photo) previews with correct orientation and no quality regression
|
||||||
|
4. `cargo test --workspace` passes with the new test included
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
**Execution Order:**
|
||||||
|
Phases execute in numeric order: 1 -> 2
|
||||||
|
|
||||||
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|
|-------|----------------|--------|-----------|
|
||||||
|
| 1. Implementation | 0/1 | Planned | - |
|
||||||
|
| 2. Verification | 0/TBD | Not started | - |
|
||||||
417
.planning/phases/01-implementation/01-01-PLAN.md
Normal file
417
.planning/phases/01-implementation/01-01-PLAN.md
Normal file
|
|
@ -0,0 +1,417 @@
|
||||||
|
---
|
||||||
|
phase: 01-implementation
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- yazi-adapter/Cargo.toml
|
||||||
|
- yazi-adapter/src/image.rs
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- RESIZE-01
|
||||||
|
- RESIZE-02
|
||||||
|
- RESIZE-03
|
||||||
|
- RESIZE-04
|
||||||
|
- RESIZE-05
|
||||||
|
- COMPAT-01
|
||||||
|
- COMPAT-02
|
||||||
|
- COMPAT-03
|
||||||
|
- COMPAT-04
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Image downscaling uses linear-light pipeline (sRGB forward map before resize, backward map after)"
|
||||||
|
- "All 5 config filter strings map to correct fast_image_resize ResizeAlg variants"
|
||||||
|
- "Unknown filter names log a warning and fall back to lanczos3"
|
||||||
|
- "Nearest-neighbor resize skips linearization (no wasted buffer allocations)"
|
||||||
|
- "DynamicImage variant (Luma8, LumaA8, Rgb8, Rgba8) is preserved after resize"
|
||||||
|
- "Both precache() and downscale() use the new fir_resize path"
|
||||||
|
- "precache() public signature is unchanged"
|
||||||
|
- "All resize work remains inside spawn_blocking closures"
|
||||||
|
artifacts:
|
||||||
|
- path: "yazi-adapter/Cargo.toml"
|
||||||
|
provides: "fast_image_resize dependency"
|
||||||
|
contains: "fast_image_resize"
|
||||||
|
- path: "yazi-adapter/src/image.rs"
|
||||||
|
provides: "Linear-light resize implementation"
|
||||||
|
exports: ["Image"]
|
||||||
|
contains: "fir_resize"
|
||||||
|
key_links:
|
||||||
|
- from: "yazi-adapter/src/image.rs"
|
||||||
|
to: "fast_image_resize crate"
|
||||||
|
via: "use fast_image_resize::{...}"
|
||||||
|
pattern: "create_srgb_mapper|Resizer::new|ResizeOptions"
|
||||||
|
- from: "yazi-adapter/src/image.rs::precache()"
|
||||||
|
to: "fir_resize helper"
|
||||||
|
via: "Self::fir_resize call replacing img.resize()"
|
||||||
|
pattern: "Self::fir_resize"
|
||||||
|
- from: "yazi-adapter/src/image.rs::downscale()"
|
||||||
|
to: "fir_resize helper"
|
||||||
|
via: "Self::fir_resize call replacing img.resize()"
|
||||||
|
pattern: "Self::fir_resize"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Replace both image resize call sites in yazi-adapter with linear-light-correct downscaling via fast_image_resize.
|
||||||
|
|
||||||
|
Purpose: The current `image` crate `resize()` convolves in gamma-encoded sRGB space, producing visibly blurry thumbnails. Switching to `fast_image_resize` with explicit sRGB linearization (forward_map before resize, backward_map after) produces correct linear-light convolution with SIMD acceleration.
|
||||||
|
|
||||||
|
Output: Modified `yazi-adapter/Cargo.toml` (new dependency) and `yazi-adapter/src/image.rs` (new resize pipeline replacing both call sites).
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@~/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/STATE.md
|
||||||
|
@.planning/phases/01-implementation/01-CONTEXT.md
|
||||||
|
@.planning/phases/01-implementation/01-RESEARCH.md
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||||
|
|
||||||
|
From yazi-adapter/src/image.rs (CURRENT — to be modified):
|
||||||
|
```rust
|
||||||
|
pub struct Image;
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
// PUBLIC — signature MUST NOT change (called by Lua plugin layer)
|
||||||
|
pub async fn precache(src: PathBuf, cache: &Path) -> Result<()>
|
||||||
|
|
||||||
|
// pub(super) — called by all protocol drivers
|
||||||
|
pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage>
|
||||||
|
|
||||||
|
// PRIVATE — to be replaced with resize_alg()
|
||||||
|
fn filter() -> FilterType
|
||||||
|
|
||||||
|
// Unchanged helpers
|
||||||
|
pub(super) fn max_pixel(rect: Rect) -> (u16, u16)
|
||||||
|
pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect
|
||||||
|
async fn decode_from(path: PathBuf) -> Result<(DynamicImage, Orientation)>
|
||||||
|
fn flip_size(orientation: Orientation, (w, h): (u16, u16)) -> (u32, u32)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From yazi-adapter/src/icc.rs (UNCHANGED — produces input to resize):
|
||||||
|
```rust
|
||||||
|
pub(super) struct Icc;
|
||||||
|
impl Icc {
|
||||||
|
// Can output: GrayImage (Luma8), GrayAlphaImage (LumaA8), RgbImage (Rgb8), RgbaImage (Rgba8)
|
||||||
|
pub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result<DynamicImage>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From yazi-config/src/preview/preview.rs (UNCHANGED — config source):
|
||||||
|
```rust
|
||||||
|
pub struct Preview {
|
||||||
|
pub image_filter: String, // "nearest", "triangle", "catmull-rom", "gaussian", "lanczos3"
|
||||||
|
pub image_quality: u8,
|
||||||
|
pub max_width: u16,
|
||||||
|
pub max_height: u16,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Add fast_image_resize dependency</name>
|
||||||
|
<files>yazi-adapter/Cargo.toml</files>
|
||||||
|
<read_first>
|
||||||
|
- yazi-adapter/Cargo.toml
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Add `fast_image_resize` to the `[dependencies]` section of `yazi-adapter/Cargo.toml`.
|
||||||
|
|
||||||
|
Insert the following line in the external dependencies block (after the `image` line, maintaining alphabetical order among external deps):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
fast_image_resize = { version = "6.0.0", features = ["image"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
The `image` feature enables zero-copy `IntoImageView`/`IntoImageViewMut` bridge for `DynamicImage` and all `ImageBuffer` variants. Do NOT add `rayon` feature (yazi uses `spawn_blocking` for parallelism). Do NOT add `only_u8x4` feature (it would disable Gray U8 and GrayAlpha U8x2 support).
|
||||||
|
|
||||||
|
No workspace-level entry in root `Cargo.toml` is needed — only `yazi-adapter` uses this library.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/D051699/git/playground/yazi && grep 'fast_image_resize' yazi-adapter/Cargo.toml && cargo check -p yazi-adapter 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- yazi-adapter/Cargo.toml contains line: `fast_image_resize = { version = "6.0.0", features = ["image"] }`
|
||||||
|
- `cargo check -p yazi-adapter` exits 0
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>fast_image_resize 6.0.0 is declared as a dependency of yazi-adapter with the image feature enabled</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Replace resize pipeline with linear-light fast_image_resize</name>
|
||||||
|
<files>yazi-adapter/src/image.rs</files>
|
||||||
|
<read_first>
|
||||||
|
- yazi-adapter/src/image.rs
|
||||||
|
- yazi-adapter/src/icc.rs
|
||||||
|
- .planning/phases/01-implementation/01-RESEARCH.md
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Rewrite `yazi-adapter/src/image.rs` to replace the `image` crate's `img.resize()` with `fast_image_resize` using explicit sRGB linearization. The file has 128 lines currently. The changes are:
|
||||||
|
|
||||||
|
**1. Update imports (line 4):**
|
||||||
|
|
||||||
|
Remove `imageops::FilterType` from the `image` use statement. Add new imports for `fast_image_resize`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use fast_image_resize::{IntoImageView, ResizeAlg, ResizeOptions, Resizer, create_srgb_mapper};
|
||||||
|
use fast_image_resize::images::Image as FirImage;
|
||||||
|
```
|
||||||
|
|
||||||
|
The full `image` import line becomes (remove `imageops::FilterType`):
|
||||||
|
```rust
|
||||||
|
use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, metadata::Orientation};
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Replace `fn filter()` (lines 86-95) with `fn resize_alg()`:**
|
||||||
|
|
||||||
|
Per D-01 (default lanczos3), D-02 (same 5 filter names), D-03 (warn + lanczos3 fallback for unknown):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn resize_alg() -> ResizeAlg {
|
||||||
|
use fast_image_resize::FilterType;
|
||||||
|
match YAZI.preview.image_filter.as_str() {
|
||||||
|
"nearest" => ResizeAlg::Nearest,
|
||||||
|
"triangle" => ResizeAlg::Convolution(FilterType::Bilinear),
|
||||||
|
"catmull-rom" => ResizeAlg::Convolution(FilterType::CatmullRom),
|
||||||
|
"gaussian" => ResizeAlg::Convolution(FilterType::Gaussian),
|
||||||
|
"lanczos3" => ResizeAlg::Convolution(FilterType::Lanczos3),
|
||||||
|
other => {
|
||||||
|
tracing::warn!("unknown image_filter {other:?}, falling back to lanczos3");
|
||||||
|
ResizeAlg::Convolution(FilterType::Lanczos3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `"triangle"` maps to `FilterType::Bilinear` (the `image` crate's `Triangle` IS bilinear interpolation; `fast_image_resize` uses the name `Bilinear`).
|
||||||
|
|
||||||
|
**3. Add `fn fir_resize()` private helper:**
|
||||||
|
|
||||||
|
This is the core of the change. It implements: sRGB linearize -> resize in linear light -> sRGB encode back. For `ResizeAlg::Nearest`, skip linearization (nearest doesn't convolve, so the mapper is wasted work).
|
||||||
|
|
||||||
|
Handle `pixel_type()` returning `None` (exotic 16-bit/float variants from `DynamicImage::from_decoder()` fallback in icc.rs) by converting to `to_rgb8()` first with a `tracing::debug!` log.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn fir_resize(img: DynamicImage, w: u32, h: u32, alg: ResizeAlg) -> anyhow::Result<DynamicImage> {
|
||||||
|
let img = match img.pixel_type() {
|
||||||
|
Some(_) => img,
|
||||||
|
None => {
|
||||||
|
tracing::debug!("converting exotic pixel type to rgb8 for resize");
|
||||||
|
img.to_rgb8().into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let pixel_type = img
|
||||||
|
.pixel_type()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("unsupported pixel type for fast_image_resize"))?;
|
||||||
|
|
||||||
|
if matches!(alg, ResizeAlg::Nearest) {
|
||||||
|
let mut dst = FirImage::new(w, h, pixel_type);
|
||||||
|
let mut resizer = Resizer::new();
|
||||||
|
resizer.resize(&img, &mut dst, &ResizeOptions::new().resize_alg(alg))?;
|
||||||
|
return Self::reconstruct(w, h, pixel_type, dst.into_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mapper = create_srgb_mapper();
|
||||||
|
|
||||||
|
// sRGB -> linear
|
||||||
|
let mut linear_src = FirImage::new(img.width(), img.height(), pixel_type);
|
||||||
|
mapper.forward_map(&img, &mut linear_src)?;
|
||||||
|
|
||||||
|
// Resize in linear light (mul_div_alpha=true by default handles alpha premult)
|
||||||
|
let mut dst = FirImage::new(w, h, pixel_type);
|
||||||
|
let mut resizer = Resizer::new();
|
||||||
|
let opts = ResizeOptions::new().resize_alg(alg);
|
||||||
|
resizer.resize(&linear_src, &mut dst, &opts)?;
|
||||||
|
|
||||||
|
// linear -> sRGB
|
||||||
|
let mut srgb_dst = FirImage::new(w, h, pixel_type);
|
||||||
|
mapper.backward_map(&dst, &mut srgb_dst)?;
|
||||||
|
|
||||||
|
Self::reconstruct(w, h, pixel_type, srgb_dst.into_vec())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Add `fn reconstruct()` private helper:**
|
||||||
|
|
||||||
|
Reconstructs the correct `DynamicImage` variant from the resized buffer bytes, matching the original pixel type. This preserves COMPAT-02 (buffer format preserved).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn reconstruct(
|
||||||
|
w: u32,
|
||||||
|
h: u32,
|
||||||
|
pixel_type: fast_image_resize::PixelType,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
) -> anyhow::Result<DynamicImage> {
|
||||||
|
use anyhow::Context;
|
||||||
|
use fast_image_resize::PixelType;
|
||||||
|
use image::{GrayAlphaImage, GrayImage, RgbImage, RgbaImage};
|
||||||
|
match pixel_type {
|
||||||
|
PixelType::U8 => {
|
||||||
|
Ok(GrayImage::from_raw(w, h, buf).context("gray reconstruct")?.into())
|
||||||
|
}
|
||||||
|
PixelType::U8x2 => {
|
||||||
|
Ok(GrayAlphaImage::from_raw(w, h, buf).context("graya reconstruct")?.into())
|
||||||
|
}
|
||||||
|
PixelType::U8x3 => {
|
||||||
|
Ok(RgbImage::from_raw(w, h, buf).context("rgb reconstruct")?.into())
|
||||||
|
}
|
||||||
|
PixelType::U8x4 => {
|
||||||
|
Ok(RgbaImage::from_raw(w, h, buf).context("rgba reconstruct")?.into())
|
||||||
|
}
|
||||||
|
_ => Err(anyhow::anyhow!("unexpected pixel type {pixel_type:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Update `precache()` call site (line 21):**
|
||||||
|
|
||||||
|
Replace:
|
||||||
|
```rust
|
||||||
|
img = img.resize(w, h, Self::filter());
|
||||||
|
```
|
||||||
|
With:
|
||||||
|
```rust
|
||||||
|
img = Self::fir_resize(img, w, h, Self::resize_alg())?;
|
||||||
|
```
|
||||||
|
|
||||||
|
The `?` operator works here because the closure already returns `Ok::<_, ImageError>(buf)` -- but note the error type changes. The closure return type must be updated from `Ok::<_, ImageError>(buf)` to `Ok::<_, anyhow::Error>(buf)` since `fir_resize` returns `anyhow::Result`. The outer `.await??` still works because `anyhow::Error` implements the required traits.
|
||||||
|
|
||||||
|
Actually, looking more carefully: the spawn_blocking closure currently returns `Result<Vec<u8>, ImageError>`. Since `fir_resize` returns `anyhow::Result<DynamicImage>`, and the `?` operator on it needs the closure to return a compatible error type, change the closure to return `anyhow::Result<Vec<u8>>`:
|
||||||
|
- Change `Ok::<_, ImageError>(buf)` to `Ok::<_, anyhow::Error>(buf)` (or just `Ok(buf)` if the compiler can infer)
|
||||||
|
- The outer `precache` function already returns `anyhow::Result<()>`, and `.await??` will work with `anyhow::Error` from the inner closure
|
||||||
|
|
||||||
|
**6. Update `downscale()` call site (line 54):**
|
||||||
|
|
||||||
|
Replace:
|
||||||
|
```rust
|
||||||
|
img = img.resize(w, h, Self::filter())
|
||||||
|
```
|
||||||
|
With:
|
||||||
|
```rust
|
||||||
|
img = Self::fir_resize(img, w, h, Self::resize_alg())?;
|
||||||
|
```
|
||||||
|
|
||||||
|
The current `spawn_blocking` closure in `downscale()` returns `DynamicImage` directly (no Result). Since `fir_resize` returns `anyhow::Result<DynamicImage>`, update the closure to return `anyhow::Result<DynamicImage>`:
|
||||||
|
- Wrap the final `img` return in `Ok(img)`
|
||||||
|
- Change `.await?` to `.await??` (first `?` unwraps JoinError, second unwraps anyhow::Error)
|
||||||
|
- The outer function already returns `Result<DynamicImage>` so this works
|
||||||
|
|
||||||
|
The updated `downscale` spawn_blocking block becomes:
|
||||||
|
```rust
|
||||||
|
let img = tokio::task::spawn_blocking(move || {
|
||||||
|
if img.width() > w || img.height() > h {
|
||||||
|
img = Self::fir_resize(img, w, h, Self::resize_alg())?;
|
||||||
|
}
|
||||||
|
if orientation != Orientation::NoTransforms {
|
||||||
|
img.apply_orientation(orientation);
|
||||||
|
}
|
||||||
|
Ok(img)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important constraints:**
|
||||||
|
- Keep all new code inside the existing `spawn_blocking` closures (COMPAT-03)
|
||||||
|
- Do NOT change `precache()` public signature `pub async fn precache(src: PathBuf, cache: &Path) -> Result<()>` (COMPAT-04)
|
||||||
|
- Use hard tabs for indentation per rustfmt.toml
|
||||||
|
- Follow import grouping: std -> external crates -> workspace crates
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/D051699/git/playground/yazi && cargo build -p yazi-adapter 2>&1 | tail -10 && cargo clippy -p yazi-adapter 2>&1 | tail -10</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- yazi-adapter/src/image.rs does NOT contain `imageops::FilterType`
|
||||||
|
- yazi-adapter/src/image.rs contains `fn resize_alg() -> ResizeAlg`
|
||||||
|
- yazi-adapter/src/image.rs contains `fn fir_resize(img: DynamicImage, w: u32, h: u32, alg: ResizeAlg)`
|
||||||
|
- yazi-adapter/src/image.rs contains `fn reconstruct(`
|
||||||
|
- yazi-adapter/src/image.rs contains `create_srgb_mapper()`
|
||||||
|
- yazi-adapter/src/image.rs contains `mapper.forward_map(` and `mapper.backward_map(`
|
||||||
|
- yazi-adapter/src/image.rs contains `matches!(alg, ResizeAlg::Nearest)` (skip linearization for nearest)
|
||||||
|
- yazi-adapter/src/image.rs contains `tracing::warn!("unknown image_filter` (D-03 fallback)
|
||||||
|
- yazi-adapter/src/image.rs contains `Self::fir_resize(img, w, h, Self::resize_alg())` in precache()
|
||||||
|
- yazi-adapter/src/image.rs contains `Self::fir_resize(img, w, h, Self::resize_alg())` in downscale()
|
||||||
|
- yazi-adapter/src/image.rs filter mapping contains `"triangle" => ResizeAlg::Convolution(FilterType::Bilinear)`
|
||||||
|
- precache() signature is still `pub async fn precache(src: PathBuf, cache: &Path) -> Result<()>`
|
||||||
|
- `cargo build -p yazi-adapter` exits 0
|
||||||
|
- `cargo clippy -p yazi-adapter` produces no errors
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Both resize call sites use fir_resize with linear-light pipeline; all 5 filter names map correctly; unknown filters warn and fallback to lanczos3; DynamicImage variant is preserved; exotic pixel types handled gracefully</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Full workspace build and lint verification</name>
|
||||||
|
<files>yazi-adapter/src/image.rs</files>
|
||||||
|
<read_first>
|
||||||
|
- yazi-adapter/src/image.rs
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Run full workspace build and clippy to verify no regressions across the entire codebase.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release 2>&1
|
||||||
|
cargo clippy --all 2>&1
|
||||||
|
cargo test --workspace 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
If clippy produces warnings in the modified code, fix them:
|
||||||
|
- Common issues: unused imports (the old `FilterType` import), missing `use Self` pattern, format_push_string
|
||||||
|
- If `IntoImageView` import triggers an "unused import" warning because it is only used as a trait impl (not called directly), add `#[allow(unused_imports)]` or verify it is needed for the `.pixel_type()` method resolution
|
||||||
|
|
||||||
|
If the release build fails, check for:
|
||||||
|
- Missing `Send` bounds on types used across `spawn_blocking` (all `fast_image_resize` types are `Send`)
|
||||||
|
- LTO compilation issues (unlikely with pure Rust dependency)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/D051699/git/playground/yazi && cargo build --release 2>&1 | tail -5 && cargo clippy --all 2>&1 | tail -5 && cargo test --workspace 2>&1 | tail -10</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `cargo build --release` exits 0
|
||||||
|
- `cargo clippy --all` exits 0 with no new warnings in yazi-adapter
|
||||||
|
- `cargo test --workspace` exits 0
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Full workspace builds and passes clippy without new warnings; all existing tests pass; release build with LTO succeeds</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
After all tasks complete, verify the full implementation:
|
||||||
|
|
||||||
|
1. **Dependency check:** `grep 'fast_image_resize' yazi-adapter/Cargo.toml` shows the dependency
|
||||||
|
2. **No old resize:** `grep 'img.resize(' yazi-adapter/src/image.rs` returns no matches (both call sites replaced)
|
||||||
|
3. **No old FilterType:** `grep 'imageops::FilterType' yazi-adapter/src/image.rs` returns no matches
|
||||||
|
4. **Linear pipeline present:** `grep 'forward_map\|backward_map' yazi-adapter/src/image.rs` shows both mapper calls
|
||||||
|
5. **Nearest optimization:** `grep 'Nearest' yazi-adapter/src/image.rs` shows the skip-linearization branch
|
||||||
|
6. **Filter mapping complete:** `grep -c 'ResizeAlg' yazi-adapter/src/image.rs` shows multiple usages
|
||||||
|
7. **Signature preserved:** `grep 'pub async fn precache' yazi-adapter/src/image.rs` shows unchanged signature
|
||||||
|
8. **Build clean:** `cargo build --release && cargo clippy --all && cargo test --workspace` all pass
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
1. `yazi-adapter/src/image.rs` uses `fast_image_resize` with `create_srgb_mapper().forward_map()` before resize and `backward_map()` after (RESIZE-01, RESIZE-02)
|
||||||
|
2. Both `precache()` and `downscale()` call `fir_resize` instead of `img.resize()` (RESIZE-03)
|
||||||
|
3. All 5 filter strings correctly mapped to ResizeAlg variants (RESIZE-04)
|
||||||
|
4. Default path uses `Lanczos3` convolution in linear light (RESIZE-05)
|
||||||
|
5. No driver files modified (COMPAT-01)
|
||||||
|
6. `reconstruct()` preserves DynamicImage variant matching input pixel type (COMPAT-02)
|
||||||
|
7. All resize code inside `spawn_blocking` closures (COMPAT-03)
|
||||||
|
8. `precache()` public signature unchanged (COMPAT-04)
|
||||||
|
9. `cargo build --release` and `cargo clippy --all` pass clean
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/01-implementation/01-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Loading…
Add table
Reference in a new issue