1- use std:: path:: { Path , PathBuf } ;
1+ use std:: { path:: { Path , PathBuf } , sync :: { Arc , LazyLock , RwLock } } ;
22
33use anyhow:: Result ;
4- use image:: { DynamicImage , ExtendedColorType , ImageDecoder , ImageEncoder , ImageError , ImageReader , ImageResult , Limits , codecs:: { jpeg:: JpegEncoder , png:: PngEncoder } , imageops:: FilterType , metadata:: Orientation } ;
4+ use image:: { DynamicImage , ExtendedColorType , ImageBuffer , ImageDecoder , ImageEncoder , ImageError , ImageReader , ImageResult , Limits , codecs:: { jpeg:: JpegEncoder , png:: PngEncoder } , error :: UnsupportedErrorKind , imageops:: FilterType , metadata:: Orientation } ;
55use ratatui:: layout:: Rect ;
6+ use resvg:: { tiny_skia:: { Pixmap , Transform } , usvg:: { Options , Tree , fontdb:: Database } } ;
67use yazi_config:: YAZI ;
78
89use crate :: Dimension ;
910
11+ pub static GLOBAL_OPTIONS : LazyLock < RwLock < Options < ' static > > > =
12+ LazyLock :: new ( || RwLock :: new ( Options :: default ( ) ) ) ;
13+
1014pub struct Image ;
1115
1216impl Image {
1317 pub async fn precache ( path : & Path , cache : PathBuf ) -> Result < ( ) > {
14- let ( mut img, orientation , icc) = Self :: decode_from ( path ) . await ? ;
15- let ( w , h ) = Self :: flip_size ( orientation , ( YAZI . preview . max_width , YAZI . preview . max_height ) ) ;
18+ let ( img, icc) =
19+ Self :: decode_to_fit_from ( path , YAZI . preview . max_width , YAZI . preview . max_height ) . await ? ;
1620
1721 let buf = tokio:: task:: spawn_blocking ( move || {
18- if img. width ( ) > w || img. height ( ) > h {
19- img = img. resize ( w, h, Self :: filter ( ) ) ;
20- }
21- if orientation != Orientation :: NoTransforms {
22- img. apply_orientation ( orientation) ;
23- }
24-
2522 let mut buf = Vec :: new ( ) ;
2623 if img. color ( ) . has_alpha ( ) {
2724 let rgba = img. into_rgba8 ( ) ;
@@ -42,25 +39,8 @@ impl Image {
4239 }
4340
4441 pub ( super ) async fn downscale ( path : & Path , rect : Rect ) -> Result < DynamicImage > {
45- let ( mut img, orientation, _) = Self :: decode_from ( path) . await ?;
46- let ( w, h) = Self :: flip_size ( orientation, Self :: max_pixel ( rect) ) ;
47-
48- // Fast path.
49- if img. width ( ) <= w && img. height ( ) <= h && orientation == Orientation :: NoTransforms {
50- return Ok ( img) ;
51- }
52-
53- let img = tokio:: task:: spawn_blocking ( move || {
54- if img. width ( ) > w || img. height ( ) > h {
55- img = img. resize ( w, h, Self :: filter ( ) )
56- }
57- if orientation != Orientation :: NoTransforms {
58- img. apply_orientation ( orientation) ;
59- }
60- img
61- } )
62- . await ?;
63-
42+ let ( width, height) = Self :: max_pixel ( rect) ;
43+ let ( img, _) = Self :: decode_to_fit_from ( path, width, height) . await ?;
6444 Ok ( img)
6545 }
6646
@@ -96,7 +76,54 @@ impl Image {
9676 }
9777 }
9878
99- async fn decode_from ( path : & Path ) -> ImageResult < ( DynamicImage , Orientation , Option < Vec < u8 > > ) > {
79+ async fn decode_to_fit_from (
80+ path : & Path ,
81+ width : u32 ,
82+ height : u32 ,
83+ ) -> Result < ( DynamicImage , Option < Vec < u8 > > ) > {
84+ let path = path. to_owned ( ) ;
85+
86+ tokio:: task:: spawn_blocking ( move || {
87+ Self :: try_decode_raster ( & path, width, height) . or_else ( |err| match err {
88+ ImageError :: Unsupported ( ref unsupported) => match unsupported. kind ( ) {
89+ UnsupportedErrorKind :: Format ( _) => Self :: try_decode_svg ( & path, width, height) ,
90+ _ => Err ( err. into ( ) ) ,
91+ } ,
92+ _ => Err ( err. into ( ) ) ,
93+ } )
94+ } )
95+ . await
96+ . map_err ( |e| ImageError :: IoError ( e. into ( ) ) ) ?
97+ }
98+
99+ #[ inline]
100+ fn try_decode_raster (
101+ path : & Path ,
102+ width : u32 ,
103+ height : u32 ,
104+ ) -> ImageResult < ( DynamicImage , Option < Vec < u8 > > ) > {
105+ let limits = Self :: build_limits ( ) ;
106+ let mut reader = ImageReader :: open ( path) ?;
107+ reader. limits ( limits) ;
108+ let mut decoder = reader. with_guessed_format ( ) ?. into_decoder ( ) ?;
109+ let orientation = decoder. orientation ( ) . unwrap_or ( Orientation :: NoTransforms ) ;
110+ let icc = decoder. icc_profile ( ) . unwrap_or_default ( ) ;
111+
112+ let mut img = DynamicImage :: from_decoder ( decoder) ?;
113+ let ( w, h) = Self :: flip_size ( orientation, ( width, height) ) ;
114+
115+ if img. width ( ) > w || img. height ( ) > h {
116+ img = img. resize ( w, h, Self :: filter ( ) ) ;
117+ }
118+ if orientation != Orientation :: NoTransforms {
119+ img. apply_orientation ( orientation) ;
120+ }
121+
122+ Ok ( ( img, icc) )
123+ }
124+
125+ #[ inline]
126+ fn build_limits ( ) -> Limits {
100127 let mut limits = Limits :: no_limits ( ) ;
101128 if YAZI . tasks . image_alloc > 0 {
102129 limits. max_alloc = Some ( YAZI . tasks . image_alloc as u64 ) ;
@@ -107,20 +134,76 @@ impl Image {
107134 if YAZI . tasks . image_bound [ 1 ] > 0 {
108135 limits. max_image_height = Some ( YAZI . tasks . image_bound [ 1 ] as u32 ) ;
109136 }
137+ limits
138+ }
110139
111- let path = path. to_owned ( ) ;
112- tokio:: task:: spawn_blocking ( move || {
113- let mut reader = ImageReader :: open ( path) ?;
114- reader. limits ( limits) ;
140+ #[ inline]
141+ fn try_decode_svg (
142+ path : & Path ,
143+ width : u32 ,
144+ height : u32 ,
145+ ) -> Result < ( DynamicImage , Option < Vec < u8 > > ) > {
146+ let pixmap = Self :: render_svg_to_fit_from ( path, width, height) ?;
147+ let ( width, height) = ( pixmap. width ( ) , pixmap. height ( ) ) ;
148+ let mut container = pixmap. take ( ) ;
149+
150+ for rgba in container. chunks_exact_mut ( 4 ) {
151+ let alpha = rgba[ 3 ] ;
152+ if alpha != 0xff {
153+ let pixel: & mut [ u8 ; 4 ] = unsafe { rgba. try_into ( ) . unwrap_unchecked ( ) } ;
154+ let a = alpha as f64 / 255.0 ;
155+ pixel[ 0 ] = ( pixel[ 0 ] as f64 / a + 0.5 ) as u8 ;
156+ pixel[ 1 ] = ( pixel[ 1 ] as f64 / a + 0.5 ) as u8 ;
157+ pixel[ 2 ] = ( pixel[ 2 ] as f64 / a + 0.5 ) as u8 ;
158+ }
159+ }
115160
116- let mut decoder = reader. with_guessed_format ( ) ?. into_decoder ( ) ?;
117- let orientation = decoder. orientation ( ) . unwrap_or ( Orientation :: NoTransforms ) ;
118- let icc = decoder. icc_profile ( ) . unwrap_or_default ( ) ;
161+ let img = ImageBuffer :: from_raw ( width, height, container)
162+ . ok_or_else ( || anyhow:: anyhow!( "Failed to create image buffer" ) ) ?;
119163
120- Ok ( ( DynamicImage :: from_decoder ( decoder) ?, orientation, icc) )
121- } )
122- . await
123- . map_err ( |e| ImageError :: IoError ( e. into ( ) ) ) ?
164+ Ok ( ( DynamicImage :: ImageRgba8 ( img) , None ) )
165+ }
166+
167+ /// Helper function to rasterize an SVG to a pixmap fitting within max_width
168+ /// and max_height.
169+ fn render_svg_to_fit_from ( path : & Path , max_width : u32 , max_height : u32 ) -> Result < Pixmap > {
170+ let svg = std:: fs:: read ( path) ?;
171+ let options_guard =
172+ GLOBAL_OPTIONS . read ( ) . map_err ( |e| anyhow:: anyhow!( "RwLock poisoned: {}" , e) ) ?;
173+ let tree = Tree :: from_data ( & svg, & options_guard) ?;
174+ let ( width, height, transform) = Self :: svg_size_and_scale ( & tree, max_width, max_height) ;
175+
176+ let mut pixmap =
177+ Pixmap :: new ( width, height) . ok_or_else ( || anyhow:: anyhow!( "Cannot create pixmap" ) ) ?;
178+
179+ resvg:: render ( & tree, transform, & mut pixmap. as_mut ( ) ) ;
180+
181+ Ok ( pixmap)
182+ }
183+
184+ fn svg_size_and_scale ( tree : & Tree , max_width : u32 , max_height : u32 ) -> ( u32 , u32 , Transform ) {
185+ // It is Ok. The max_width and max_height could not be larger then monitor
186+ // dimensions which much less then f32::MAX
187+ let max_width = max_width as f32 ;
188+ let max_height = max_height as f32 ;
189+ let mut width = tree. size ( ) . width ( ) ;
190+ let mut height = tree. size ( ) . height ( ) ;
191+
192+ for node in tree. root ( ) . children ( ) {
193+ if let Some ( bounding_box) = node. abs_layer_bounding_box ( ) {
194+ width = width. max ( bounding_box. width ( ) ) ;
195+ height = height. max ( bounding_box. height ( ) ) ;
196+ }
197+ }
198+ if width <= max_width && height <= max_height {
199+ return ( width. floor ( ) as u32 , height. floor ( ) as u32 , Transform :: from_scale ( 1.0 , 1.0 ) ) ;
200+ }
201+ let ratio = f32:: min ( max_width / width, max_height / height) ;
202+ (
203+ ( width * ratio) . floor ( ) as u32 ,
204+ ( height * ratio) . floor ( ) as u32 ,
205+ Transform :: from_scale ( ratio, ratio) ,
206+ )
124207 }
125208
126209 fn flip_size ( orientation : Orientation , ( w, h) : ( u32 , u32 ) ) -> ( u32 , u32 ) {
0 commit comments