@@ -10,9 +10,12 @@ use tracing::instrument;
1010
1111/// Helper function to rasterize SVG and convert to RgbaImage.
1212/// This eliminates code duplication across encoders.
13- fn rasterize_svg_to_rgba ( svg_data : & str ) -> Result < RgbaImage > {
14- let rasterizer = crate :: image:: Rasterizer :: new ( ) ;
15- let pixmap = rasterizer. render ( svg_data) ?;
13+ fn rasterize_svg_to_rgba (
14+ rasterizer : & crate :: image:: Rasterizer ,
15+ svg_data : & str ,
16+ scale : Option < f64 > ,
17+ ) -> Result < RgbaImage > {
18+ let pixmap = rasterizer. render_with_scale ( svg_data, scale) ?;
1619
1720 let width = pixmap. width ( ) ;
1821 let height = pixmap. height ( ) ;
@@ -82,10 +85,11 @@ pub trait Encoder {
8285 /// # Arguments
8386 /// * `svg_data` - The SVG data to encode
8487 /// * `writer` - Output writer for the encoded data
88+ /// * `scale` - Optional scale factor for the image
8589 ///
8690 /// # Returns
8791 /// Result indicating success or failure
88- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > ;
92+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > ;
8993}
9094
9195/// PNG encoder using the resvg library.
@@ -103,9 +107,11 @@ impl PngEncoder {
103107}
104108
105109impl Encoder for PngEncoder {
106- #[ instrument( skip( self , writer) ) ]
107- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
108- let pixmap = self . rasterizer . render ( svg_data) ?;
110+ #[ instrument( skip( self , writer, svg_data) ) ]
111+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > {
112+ let start_time = std:: time:: Instant :: now ( ) ;
113+
114+ let pixmap = self . rasterizer . render_with_scale ( svg_data, scale) ?;
109115
110116 let mut png_encoder = png:: Encoder :: new ( writer, pixmap. width ( ) , pixmap. height ( ) ) ;
111117 png_encoder. set_color ( png:: ColorType :: Rgba ) ;
@@ -123,6 +129,27 @@ impl Encoder for PngEncoder {
123129 . finish ( )
124130 . map_err ( |e| LivecardsError :: Image ( ImageError :: PngWrite ( e. to_string ( ) ) ) ) ?;
125131
132+ let duration = start_time. elapsed ( ) ;
133+ let duration_ms = duration. as_millis ( ) ;
134+
135+ tracing:: debug!(
136+ "PNG encoding completed in {}ms (scale: {:?}, size: {}x{})" ,
137+ duration_ms,
138+ scale,
139+ pixmap. width( ) ,
140+ pixmap. height( )
141+ ) ;
142+
143+ if duration_ms > 1000 {
144+ tracing:: warn!(
145+ "PNG encoding took {}ms (>1000ms) (scale: {:?}, size: {}x{})" ,
146+ duration_ms,
147+ scale,
148+ pixmap. width( ) ,
149+ pixmap. height( )
150+ ) ;
151+ }
152+
126153 Ok ( ( ) )
127154 }
128155}
@@ -144,9 +171,9 @@ impl WebPEncoder {
144171}
145172
146173impl Encoder for WebPEncoder {
147- #[ instrument( skip( writer) ) ]
148- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
149- let img = rasterize_svg_to_rgba ( svg_data) ?;
174+ #[ instrument( skip( writer, svg_data ) ) ]
175+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > {
176+ let img = rasterize_svg_to_rgba ( & crate :: image :: Rasterizer :: new ( ) , svg_data, scale ) ?;
150177
151178 // Encode as WebP
152179 img. write_with_encoder ( image:: codecs:: webp:: WebPEncoder :: new_lossless ( writer) )
@@ -173,9 +200,9 @@ impl JpegEncoder {
173200}
174201
175202impl Encoder for JpegEncoder {
176- #[ instrument( skip( writer) ) ]
177- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
178- let img = rasterize_svg_to_rgba ( svg_data) ?;
203+ #[ instrument( skip( writer, svg_data ) ) ]
204+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > {
205+ let img = rasterize_svg_to_rgba ( & crate :: image :: Rasterizer :: new ( ) , svg_data, scale ) ?;
179206
180207 // Convert RGBA to RGB for JPEG encoding
181208 let rgb_img = image:: DynamicImage :: ImageRgba8 ( img) . into_rgb8 ( ) ;
@@ -206,11 +233,12 @@ impl SvgEncoder {
206233}
207234
208235impl Encoder for SvgEncoder {
209- #[ instrument( skip( writer) ) ]
210- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
236+ #[ instrument( skip( writer, svg_data ) ) ]
237+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , _scale : Option < f64 > ) -> Result < ( ) > {
211238 writer
212239 . write_all ( svg_data. as_bytes ( ) )
213240 . map_err ( |e| LivecardsError :: Image ( ImageError :: SvgWrite ( e. to_string ( ) ) ) ) ?;
241+
214242 Ok ( ( ) )
215243 }
216244}
@@ -232,9 +260,9 @@ impl AvifEncoder {
232260}
233261
234262impl Encoder for AvifEncoder {
235- #[ instrument( skip( writer) ) ]
236- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
237- let img = rasterize_svg_to_rgba ( svg_data) ?;
263+ #[ instrument( skip( writer, svg_data ) ) ]
264+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > {
265+ let img = rasterize_svg_to_rgba ( & crate :: image :: Rasterizer :: new ( ) , svg_data, scale ) ?;
238266
239267 // Encode as AVIF with maximum speed settings (speed 10, quality 60)
240268 img. write_with_encoder ( image:: codecs:: avif:: AvifEncoder :: new_with_speed_quality (
@@ -265,10 +293,10 @@ impl GifEncoder {
265293
266294impl Encoder for GifEncoder {
267295 #[ instrument( skip( _svg_data, _writer) ) ]
268- fn encode ( & self , _svg_data : & str , _writer : & mut dyn Write ) -> Result < ( ) > {
296+ fn encode ( & self , _svg_data : & str , _writer : & mut dyn Write , _scale : Option < f64 > ) -> Result < ( ) > {
269297 // GIF encoding is not currently supported
270298 Err ( LivecardsError :: Image ( ImageError :: GifWrite (
271- "GIF encoding not supported " . to_string ( ) ,
299+ "GIF encoding is not implemented " . to_string ( ) ,
272300 ) ) )
273301 }
274302}
@@ -290,9 +318,9 @@ impl IcoEncoder {
290318}
291319
292320impl Encoder for IcoEncoder {
293- #[ instrument( skip( writer) ) ]
294- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
295- let img = rasterize_svg_to_rgba ( svg_data) ?;
321+ #[ instrument( skip( writer, svg_data ) ) ]
322+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > {
323+ let img = rasterize_svg_to_rgba ( & crate :: image :: Rasterizer :: new ( ) , svg_data, scale ) ?;
296324
297325 // Resize image to fit ICO requirements (max 256x256)
298326 let width = img. width ( ) ;
@@ -339,15 +367,15 @@ pub enum EncoderType {
339367}
340368
341369impl Encoder for EncoderType {
342- fn encode ( & self , svg_data : & str , writer : & mut dyn Write ) -> Result < ( ) > {
370+ fn encode ( & self , svg_data : & str , writer : & mut dyn Write , scale : Option < f64 > ) -> Result < ( ) > {
343371 match self {
344- EncoderType :: Png ( encoder) => encoder. encode ( svg_data, writer) ,
345- EncoderType :: WebP ( encoder) => encoder. encode ( svg_data, writer) ,
346- EncoderType :: Jpeg ( encoder) => encoder. encode ( svg_data, writer) ,
347- EncoderType :: Svg ( encoder) => encoder. encode ( svg_data, writer) ,
348- EncoderType :: Avif ( encoder) => encoder. encode ( svg_data, writer) ,
349- EncoderType :: Gif ( encoder) => encoder. encode ( svg_data, writer) ,
350- EncoderType :: Ico ( encoder) => encoder. encode ( svg_data, writer) ,
372+ EncoderType :: Png ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
373+ EncoderType :: WebP ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
374+ EncoderType :: Jpeg ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
375+ EncoderType :: Svg ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
376+ EncoderType :: Avif ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
377+ EncoderType :: Gif ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
378+ EncoderType :: Ico ( encoder) => encoder. encode ( svg_data, writer, scale ) ,
351379 }
352380 }
353381}
@@ -364,46 +392,3 @@ pub fn create_encoder(format: ImageFormat) -> EncoderType {
364392 ImageFormat :: Ico => EncoderType :: Ico ( IcoEncoder :: new ( ) ) ,
365393 }
366394}
367-
368- /// Generate an image in the specified format.
369- ///
370- /// # Arguments
371- /// * `name` - Repository name
372- /// * `description` - Repository description
373- /// * `language` - Primary programming language
374- /// * `stars` - Star count as string
375- /// * `forks` - Fork count as string
376- /// * `format` - Target image format
377- /// * `writer` - Output writer for the encoded data
378- ///
379- /// # Returns
380- /// Result indicating success or failure
381- #[ instrument( skip( writer) ) ]
382- pub fn generate_image < W : Write > (
383- name : & str ,
384- description : & str ,
385- language : & str ,
386- stars : & str ,
387- forks : & str ,
388- format : ImageFormat ,
389- mut writer : W ,
390- ) -> Result < ( ) > {
391- let svg_template = include_str ! ( "../card.svg" ) ;
392- let wrapped_description = crate :: image:: wrap_text ( description, 65 ) ;
393- let language_color =
394- crate :: colors:: get_color ( language) . unwrap_or_else ( || "#f1e05a" . to_string ( ) ) ;
395-
396- let formatted_stars = crate :: image:: format_count ( stars) ;
397- let formatted_forks = crate :: image:: format_count ( forks) ;
398-
399- let svg_filled = svg_template
400- . replace ( "{{name}}" , name)
401- . replace ( "{{description}}" , & wrapped_description)
402- . replace ( "{{language}}" , language)
403- . replace ( "{{language_color}}" , & language_color)
404- . replace ( "{{stars}}" , & formatted_stars)
405- . replace ( "{{forks}}" , & formatted_forks) ;
406-
407- let encoder = create_encoder ( format) ;
408- encoder. encode ( & svg_filled, & mut writer)
409- }
0 commit comments