swash/scale/bitmap/
mod.rs

1//! Bitmap scaling.
2
3#![allow(dead_code)]
4
5use alloc::vec::Vec;
6#[cfg(feature = "libm")]
7use core_maths::CoreFloat;
8
9mod png;
10
11/// Decodes a PNG image.
12pub fn decode_png(data: &[u8], scratch: &mut Vec<u8>, target: &mut [u8]) -> Option<(u32, u32)> {
13    png::decode(data, scratch, target)
14        .map(|(w, h, _)| (w, h))
15        .ok()
16}
17
18pub fn blit(
19    mask: &[u8],
20    mask_width: u32,
21    mask_height: u32,
22    x: i32,
23    y: i32,
24    color: [u8; 4],
25    target: &mut [u8],
26    target_width: u32,
27    target_height: u32,
28) {
29    if mask_width == 0 || mask_height == 0 || target_width == 0 || target_height == 0 {
30        return;
31    }
32    let source_width = mask_width as usize;
33    let source_height = mask_height as usize;
34    let dest_width = target_width as usize;
35    let dest_height = target_height as usize;
36    let source_x = if x < 0 { -x as usize } else { 0 };
37    let source_y = if y < 0 { -y as usize } else { 0 };
38    if source_x >= source_width || source_y >= source_height {
39        return;
40    }
41    let dest_x = if x < 0 { 0 } else { x as usize };
42    let dest_y = if y < 0 { 0 } else { y as usize };
43    if dest_x >= dest_width || dest_y >= dest_height {
44        return;
45    }
46    let source_end_x = (source_width).min(dest_width - dest_x + source_x);
47    let source_end_y = (source_height).min(dest_height - dest_y + source_y);
48    let source_columns = source_y..source_end_y;
49    let source_rows = source_x..source_end_x;
50    let dest_pitch = target_width as usize * 4;
51    let color_a = color[3] as u32;
52    let mut dy = dest_y;
53    for sy in source_columns {
54        let src_row = &mask[sy * mask_width as usize..];
55        let dst_row = &mut target[dy * dest_pitch..];
56        dy += 1;
57        let mut dx = dest_x * 4;
58        for sx in source_rows.clone() {
59            let a = (src_row[sx] as u32 * color_a) >> 8;
60            if a >= 255 {
61                dst_row[dx + 3] = 255;
62                dst_row[dx..(dx + 3)].copy_from_slice(&color[..3]);
63                dst_row[dx + 3] = 255;
64            } else if a != 0 {
65                let inverse_a = 255 - a;
66                for i in 0..3 {
67                    let d = dst_row[dx + i] as u32;
68                    let c = ((inverse_a * d) + (a * color[i] as u32)) >> 8;
69                    dst_row[dx + i] = c as u8;
70                }
71                let d = dst_row[dx + 3] as u32;
72                let c = ((inverse_a * d) + a * 255) >> 8;
73                dst_row[dx + 3] = c as u8;
74            }
75            dx += 4;
76        }
77    }
78}
79
80#[derive(Copy, Clone, PartialEq)]
81pub enum Filter {
82    Nearest,
83    Bilinear,
84    Bicubic,
85    Mitchell,
86    Lanczos3,
87    Gaussian,
88}
89
90pub fn resize(
91    image: &[u8],
92    width: u32,
93    height: u32,
94    channels: u32,
95    target: &mut [u8],
96    target_width: u32,
97    target_height: u32,
98    filter: Filter,
99    scratch: Option<&mut Vec<u8>>,
100) -> bool {
101    if target_width == 0 || target_height == 0 {
102        return true;
103    }
104    let mut tmp = Vec::new();
105    let scratch = if let Some(scratch) = scratch {
106        scratch
107    } else {
108        &mut tmp
109    };
110    let image_size = (width * height * channels) as usize;
111    if image.len() < image_size {
112        return false;
113    }
114    let target_size = (target_width * target_height * channels) as usize;
115    if target.len() < target_size {
116        return false;
117    }
118    let scratch_size = (target_width * height * channels) as usize;
119    scratch.resize(scratch_size, 0);
120    use Filter::*;
121    match filter {
122        Nearest => resample(
123            image,
124            width,
125            height,
126            channels,
127            target,
128            target_width,
129            target_height,
130            scratch,
131            0.,
132            &nearest,
133        ),
134        Bilinear => resample(
135            image,
136            width,
137            height,
138            channels,
139            target,
140            target_width,
141            target_height,
142            scratch,
143            1.,
144            &bilinear,
145        ),
146        Bicubic => resample(
147            image,
148            width,
149            height,
150            channels,
151            target,
152            target_width,
153            target_height,
154            scratch,
155            2.,
156            &bicubic,
157        ),
158        Mitchell => resample(
159            image,
160            width,
161            height,
162            channels,
163            target,
164            target_width,
165            target_height,
166            scratch,
167            2.,
168            &mitchell,
169        ),
170        Lanczos3 => resample(
171            image,
172            width,
173            height,
174            channels,
175            target,
176            target_width,
177            target_height,
178            scratch,
179            3.,
180            &lanczos3,
181        ),
182        Gaussian => resample(
183            image,
184            width,
185            height,
186            channels,
187            target,
188            target_width,
189            target_height,
190            scratch,
191            3.,
192            &|x| gaussian(x, 0.5),
193        ),
194    }
195}
196
197fn resample<Filter>(
198    image: &[u8],
199    width: u32,
200    height: u32,
201    channels: u32,
202    target: &mut [u8],
203    target_width: u32,
204    target_height: u32,
205    scratch: &mut [u8],
206    support: f32,
207    filter: &Filter,
208) -> bool
209where
210    Filter: Fn(f32) -> f32,
211{
212    let tmp_width = target_width;
213    let tmp_height = height;
214    let s = 1. / 255.;
215    if channels == 1 {
216        sample_dir(
217            &|x, y| [0., 0., 0., image[(y * width + x) as usize] as f32 * s],
218            width,
219            height,
220            target_width,
221            filter,
222            support,
223            &mut |x, y, p| scratch[(y * tmp_width + x) as usize] = (p[3] * 255.) as u8,
224        );
225        sample_dir(
226            &|y, x| [0., 0., 0., scratch[(y * tmp_width + x) as usize] as f32 * s],
227            tmp_height,
228            tmp_width,
229            target_height,
230            filter,
231            support,
232            &mut |y, x, p| target[(y * target_width + x) as usize] = (p[3] * 255.) as u8,
233        );
234        true
235    } else if channels == 4 {
236        sample_dir(
237            &|x, y| {
238                let row = (y * width * channels + x * channels) as usize;
239                [
240                    image[row] as f32 * s,
241                    image[row + 1] as f32 * s,
242                    image[row + 2] as f32 * s,
243                    image[row + 3] as f32 * s,
244                ]
245            },
246            width,
247            height,
248            target_width,
249            filter,
250            support,
251            &mut |x, y, p| {
252                let row = (y * target_width * channels + x * channels) as usize;
253                scratch[row] = (p[0] * 255.) as u8;
254                scratch[row + 1] = (p[1] * 255.) as u8;
255                scratch[row + 2] = (p[2] * 255.) as u8;
256                scratch[row + 3] = (p[3] * 255.) as u8;
257            },
258        );
259        sample_dir(
260            &|y, x| {
261                let row = (y * tmp_width * channels + x * channels) as usize;
262                [
263                    scratch[row] as f32 * s,
264                    scratch[row + 1] as f32 * s,
265                    scratch[row + 2] as f32 * s,
266                    scratch[row + 3] as f32 * s,
267                ]
268            },
269            tmp_height,
270            tmp_width,
271            target_height,
272            filter,
273            support,
274            &mut |y, x, p| {
275                let row = (y * target_width * channels + x * channels) as usize;
276                target[row] = (p[0] * 255.) as u8;
277                target[row + 1] = (p[1] * 255.) as u8;
278                target[row + 2] = (p[2] * 255.) as u8;
279                target[row + 3] = (p[3] * 255.) as u8;
280            },
281        );
282        true
283    } else {
284        false
285    }
286}
287
288fn sample_dir<Input, Output, Filter>(
289    input: &Input,
290    width: u32,
291    height: u32,
292    new_width: u32,
293    filter: &Filter,
294    support: f32,
295    output: &mut Output,
296) where
297    Input: Fn(u32, u32) -> [f32; 4],
298    Output: FnMut(u32, u32, &[f32; 4]),
299    Filter: Fn(f32) -> f32,
300{
301    const MAX_WEIGHTS: usize = 64;
302    let mut weights = [0f32; MAX_WEIGHTS];
303    let mut num_weights;
304    let ratio = width as f32 / new_width as f32;
305    let sratio = ratio.max(1.);
306    let src_support = support * sratio;
307    let isratio = 1. / sratio;
308    for outx in 0..new_width {
309        let inx = (outx as f32 + 0.5) * ratio;
310        let left = (inx - src_support).floor() as i32;
311        let mut left = left.max(0).min(width as i32 - 1) as usize;
312        let right = (inx + src_support).ceil() as i32;
313        let mut right = right.max(left as i32 + 1).min(width as i32) as usize;
314        let inx = inx - 0.5;
315        while right - left > MAX_WEIGHTS {
316            right -= 1;
317            left += 1;
318        }
319        num_weights = 0;
320        let mut sum = 0.;
321        for i in left..right {
322            let w = filter((i as f32 - inx) * isratio);
323            weights[num_weights] = w;
324            num_weights += 1;
325            sum += w;
326        }
327        let isum = 1. / sum;
328        let weights = &weights[..num_weights];
329        for y in 0..height {
330            let mut accum = [0f32; 4];
331            for (i, w) in weights.iter().enumerate() {
332                let p = input((left + i) as u32, y);
333                let a = p[3];
334                accum[0] += p[0] * w * a;
335                accum[1] += p[1] * w * a;
336                accum[2] += p[2] * w * a;
337                accum[3] += p[3] * w;
338            }
339            if accum[3] != 0. {
340                let a = 1. / accum[3];
341                accum[0] *= a;
342                accum[1] *= a;
343                accum[2] *= a;
344                accum[3] *= isum;
345            }
346            output(outx, y, &accum);
347        }
348    }
349}
350
351fn sinc(t: f32) -> f32 {
352    let a = t * core::f32::consts::PI;
353    if t == 0. {
354        1.
355    } else {
356        a.sin() / a
357    }
358}
359
360fn lanczos3(x: f32) -> f32 {
361    if x.abs() < 3. {
362        (sinc(x) * sinc(x / 3.)).abs()
363    } else {
364        0.
365    }
366}
367
368fn bilinear(x: f32) -> f32 {
369    let x = x.abs();
370    if x < 1. {
371        1. - x
372    } else {
373        0.
374    }
375}
376
377fn bicubic(x: f32) -> f32 {
378    let a = x.abs();
379    let b = 0.;
380    let c = 0.5;
381    let k = if a < 1. {
382        (12. - 9. * b - 6. * c) * a.powi(3) + (-18. + 12. * b + 6. * c) * a.powi(2) + (6. - 2. * b)
383    } else if a < 2. {
384        (-b - 6. * c) * a.powi(3)
385            + (6. * b + 30. * c) * a.powi(2)
386            + (-12. * b - 48. * c) * a
387            + (8. * b + 24. * c)
388    } else {
389        0.
390    };
391    (k / 6.).abs()
392}
393
394fn mitchell(x: f32) -> f32 {
395    let x = x.abs();
396    if x < 1. {
397        ((16. + x * x * (21. * x - 36.)) / 18.).abs()
398    } else if x < 2. {
399        ((32. + x * (-60. + x * (36. - 7. * x))) / 18.).abs()
400    } else {
401        0.
402    }
403}
404
405fn nearest(_x: f32) -> f32 {
406    1.
407}
408
409fn gaussian(x: f32, r: f32) -> f32 {
410    ((2. * core::f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2. * r.powi(2))).exp()
411}