swash/scale/bitmap/
png.rs

1//! PNG decoder.
2
3use alloc::vec::Vec;
4
5/// PNG magic bytes.
6pub const SIGNATURE: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
7
8/// Errors that can occur during decoding.
9#[derive(Debug)]
10pub enum DecodeError {
11    /// The file format was not supported.
12    UnsupportedFileFormat,
13    /// Conversion into the requested format failed.
14    ConversionFailed,
15    /// Invalid signature in an image.
16    InvalidSignature,
17    /// The file contains a pixel format this is not supported.
18    UnsupportedPixelFormat,
19    /// The file enables a feature that is not supported.
20    UnsupportedFeature,
21    /// Some size limit was exceeded.
22    LimitExceeded,
23    /// Some index into the image was out of bounds.
24    IndexOutOfBounds,
25    /// Some portion of the file was corrupt.
26    CorruptData,
27    /// An "end of file" was reached prematurely.
28    UnexpectedEof,
29}
30
31impl From<yazi::Error> for DecodeError {
32    fn from(_: yazi::Error) -> Self {
33        Self::CorruptData
34    }
35}
36
37/// The possible color types for a PNG image.
38#[derive(Copy, Clone, PartialEq, Eq, Debug)]
39pub enum ColorType {
40    Greyscale = 0,
41    GreyscaleAlpha = 4,
42    Indexed = 3,
43    TrueColor = 2,
44    TrueColorAlpha = 6,
45}
46
47/// The PNG header.
48#[derive(Copy, Clone, Debug)]
49pub struct Header {
50    pub width: u32,
51    pub height: u32,
52    pub color_type: ColorType,
53    pub depth: u8,
54    pub interlaced: bool,
55}
56
57impl Header {
58    /// Attempts to decode a PNG header from the specified buffer.
59    pub fn decode(png: &[u8]) -> Option<Self> {
60        const IHDR: u32 = 73 << 24 | 72 << 16 | 68 << 8 | 82;
61        if png.len() < 33 || !check_signature(png) {
62            return None;
63        }
64        let mut o = 8;
65        let len = get_u32be(png, o);
66        if len != 13 || get_u32be(png, o + 4) != IHDR {
67            return None;
68        }
69        o += 4;
70        let width = get_u32be(png, o + 4);
71        let height = get_u32be(png, o + 8);
72        let depth = png[o + 12];
73        let color_type = png[o + 13];
74        let compression_method = png[o + 14];
75        let filter_method = png[o + 15];
76        let interlace_method = png[o + 16];
77        if compression_method != 0
78            || filter_method != 0
79            || (interlace_method != 0 && interlace_method != 1)
80        {
81            return None;
82        }
83        let _crc = get_u32be(png, o + 17);
84        let d = depth;
85        use ColorType::*;
86        let color_type = match color_type {
87            0 => Greyscale,
88            2 => TrueColor,
89            3 => Indexed,
90            4 => GreyscaleAlpha,
91            6 => TrueColorAlpha,
92            _ => return None,
93        };
94        match color_type {
95            Greyscale | Indexed => {
96                if d != 1 && d != 2 && d != 4 && d != 8 && d != 16 {
97                    return None;
98                }
99                if color_type == Indexed && d == 16 {
100                    return None;
101                }
102            }
103            TrueColor | TrueColorAlpha | GreyscaleAlpha => {
104                if d != 8 && d != 16 {
105                    return None;
106                }
107            }
108        };
109        Some(Self {
110            width,
111            height,
112            color_type,
113            depth,
114            interlaced: interlace_method != 0,
115        })
116    }
117}
118
119/// Returns true if the specified buffer might represent a PNG image.
120pub fn check_signature(png: &[u8]) -> bool {
121    if png.len() >= 8 {
122        for i in 0..8 {
123            if png[i] != SIGNATURE[i] {
124                return false;
125            }
126        }
127        return true;
128    }
129    false
130}
131
132pub fn decode(
133    data: &[u8],
134    scratch: &mut Vec<u8>,
135    target: &mut [u8],
136) -> Result<(u32, u32, bool), DecodeError> {
137    let mut state = State::new(data, scratch)?;
138    let w = state.header.width;
139    let h = state.header.height;
140    if w == 0 || h == 0 {
141        return Ok((w, h, false));
142    }
143    let decomp_len = scratch.len();
144    scratch.resize(decomp_len + state.extra_bytes, 0);
145    let (decomp, extra) = scratch.split_at_mut(decomp_len);
146    if target.len() < (w * h * 4) as usize {
147        return Err(DecodeError::LimitExceeded);
148    }
149    state.trunc_16 = true;
150    state.expand_alpha = true;
151    decode_data::<EmitRgba8>(&mut state, decomp, extra, target).ok_or(DecodeError::CorruptData)?;
152    Ok((w, h, state.has_alpha))
153}
154
155struct State<'a> {
156    header: Header,
157    data: &'a [u8],
158    palette: &'a [u8],
159    trans: &'a [u8],
160    gamma: Option<f32>,
161    effective_depth: u8,
162    bpp: usize,
163    channels: usize,
164    pitch: usize,
165    extra_bytes: usize,
166    bwidth: usize,
167    has_alpha: bool,
168    trunc_16: bool,
169    expand_alpha: bool,
170}
171
172impl<'a> State<'a> {
173    fn new(data: &'a [u8], decomp: &mut Vec<u8>) -> Result<Self, DecodeError> {
174        let header = Header::decode(data).ok_or(DecodeError::CorruptData)?;
175        let mut this = Self {
176            header,
177            data,
178            palette: &[],
179            trans: &[],
180            gamma: None,
181            effective_depth: header.depth,
182            bpp: 0,
183            channels: 0,
184            pitch: 0,
185            extra_bytes: 0,
186            bwidth: 0,
187            has_alpha: false,
188            trunc_16: false,
189            expand_alpha: false,
190        };
191        let w = header.width as usize;
192        let h = header.height as usize;
193        if w == 0 || h == 0 {
194            return Ok(this);
195        }
196        use ColorType::*;
197        let (channels, has_alpha) = match header.color_type {
198            TrueColor => (3, false),
199            TrueColorAlpha => (4, true),
200            GreyscaleAlpha => (2, true),
201            Greyscale => (1, false),
202            Indexed => (1, false),
203        };
204        this.has_alpha = has_alpha;
205        this.bpp = this.header.depth as usize * channels;
206        this.pitch = (w * this.bpp + 7) / 8;
207        this.bwidth = (this.bpp + 7) / 8;
208        this.extra_bytes = this.pitch * 2 + w * 8;
209        decomp.clear();
210        decomp.reserve(this.extra_bytes + (this.pitch + 1) * h);
211        let limit = data.len();
212        let mut offset = 33;
213        let mut dec = yazi::Decoder::new();
214        dec.set_format(yazi::Format::Zlib);
215        let mut stream = dec.stream_into_vec(decomp);
216        loop {
217            if offset + 8 > limit {
218                return Err(DecodeError::CorruptData);
219            }
220            let len = get_u32be(data, offset) as usize;
221            offset += 4;
222            let ty = get_u32be(data, offset);
223            offset += 4;
224            if offset + len > limit {
225                return Err(DecodeError::CorruptData);
226            }
227            let bytes = data
228                .get(offset..offset + len)
229                .ok_or(DecodeError::CorruptData)?;
230            const PLTE: u32 = chunk_name(b"PLTE");
231            const TRNS: u32 = chunk_name(b"tRNS");
232            const IDAT: u32 = chunk_name(b"IDAT");
233            const GAMA: u32 = chunk_name(b"gAMA");
234            const IEND: u32 = chunk_name(b"IEND");
235            match ty {
236                PLTE => this.palette = bytes,
237                TRNS => this.trans = bytes,
238                IDAT => {
239                    stream.write(bytes)?;
240                }
241                GAMA => {
242                    if bytes.len() > 4 && this.gamma.is_none() {
243                        this.gamma = Some(get_u32be(bytes, 0) as f32 / 100000.);
244                    }
245                }
246                IEND => break,
247                _ => {}
248            }
249            offset += len + 4;
250        }
251        stream.finish()?;
252        if header.color_type == Indexed {
253            if this.palette.is_empty() {
254                return Err(DecodeError::CorruptData);
255            }
256            if !this.trans.is_empty() {
257                this.has_alpha = true;
258            }
259        }
260        Ok(this)
261    }
262}
263
264fn decode_data<E: Emit>(
265    state: &mut State,
266    decomp: &mut [u8],
267    extra: &mut [u8],
268    target: &mut [u8],
269) -> Option<()> {
270    let has_palette = !state.palette.is_empty();
271    let w = state.header.width as usize;
272    let h = state.header.height as usize;
273    let (mut line, extra) = extra.split_at_mut(state.pitch);
274    let (mut prev_line, out_line) = extra.split_at_mut(state.pitch);
275    let depth = state.header.depth;
276    let bpp = state.bpp;
277    let pitch = state.pitch;
278    let bwidth = state.bwidth;
279    let trunc_16 = state.trunc_16;
280    if state.header.interlaced {
281        const ROW_START: [u8; 7] = [0, 0, 4, 0, 2, 0, 1];
282        const ROW_INCREMENT: [u8; 7] = [8, 8, 8, 4, 4, 2, 2];
283        const COL_START: [u8; 7] = [0, 4, 0, 2, 0, 1, 0];
284        const COL_INCREMENT: [u8; 7] = [8, 8, 4, 4, 2, 2, 1];
285        let mut pass = 0;
286        let mut y = 0;
287        let mut offset = 0;
288        loop {
289            let cols = match pass {
290                0 => (w + 7) / 8,
291                1 => (w + 3) / 8,
292                2 => (w + 3) / 4,
293                3 => (w + 1) / 4,
294                4 => (w + 1) / 2,
295                5 => w / 2,
296                6 => w,
297                _ => return None,
298            };
299            if cols == 0 {
300                pass += 1;
301                continue;
302            }
303            let start = COL_START[pass] as usize;
304            let inc = COL_INCREMENT[pass] as usize;
305            let row_inc = ROW_INCREMENT[pass] as usize;
306            while y < h {
307                let pitch = (cols * bpp + 7) / 8;
308                let end = offset + pitch + 1;
309                let source = decomp.get(offset..end)?;
310                offset = end;
311                let ty = source[0];
312                defilter(
313                    ty,
314                    source.get(1..)?,
315                    line.get_mut(..pitch)?,
316                    prev_line.get(..pitch)?,
317                    bwidth,
318                )?;
319                if depth == 8 {
320                    E::emit(state, line, target, start, y, w, inc, cols)?;
321                } else {
322                    normalize(line, out_line, depth, has_palette, cols, trunc_16)?;
323                    E::emit(state, out_line, target, start, y, w, inc, cols)?;
324                }
325                core::mem::swap(&mut prev_line, &mut line);
326                y += row_inc;
327            }
328            if pass == 6 {
329                break;
330            }
331            pass += 1;
332            y = ROW_START[pass] as usize;
333            for byte in prev_line.iter_mut() {
334                *byte = 0;
335            }
336        }
337    } else if depth == 8 {
338        for y in 0..h {
339            let offset = y * (pitch + 1);
340            let end = offset + pitch + 1;
341            let source = decomp.get(offset..end)?;
342            let ty = *source.get(0)?;
343            defilter(ty, source.get(1..)?, line, prev_line, bwidth)?;
344            E::emit(state, line, target, 0, y, w, 1, w)?;
345            core::mem::swap(&mut prev_line, &mut line);
346        }
347    } else {
348        for y in 0..h {
349            let offset = y * (pitch + 1);
350            let end = offset + pitch + 1;
351            let source = decomp.get(offset..end)?;
352            let ty = *source.get(0)?;
353            defilter(ty, source.get(1..)?, line, prev_line, bwidth)?;
354            normalize(line, out_line, depth, has_palette, w, trunc_16)?;
355            E::emit(state, out_line, target, 0, y, w, 1, w)?;
356            core::mem::swap(&mut prev_line, &mut line);
357        }
358    }
359    Some(())
360}
361
362#[cfg(target_endian = "little")]
363const IS_LITTLE_ENDIAN: bool = true;
364
365#[cfg(not(target_endian = "little"))]
366const IS_LITTLE_ENDIAN: bool = false;
367
368fn defilter(ty: u8, source: &[u8], dest: &mut [u8], last: &[u8], bwidth: usize) -> Option<()> {
369    let len = source.len();
370    match ty {
371        0 => {
372            dest.copy_from_slice(source);
373        }
374        1 => {
375            dest.get_mut(..bwidth)?
376                .copy_from_slice(source.get(..bwidth)?);
377            for i in bwidth..len {
378                dest[i] = source[i].wrapping_add(dest[i - bwidth]);
379            }
380        }
381        2 => {
382            for ((dest, source), last) in dest.iter_mut().zip(source.iter()).zip(last.iter()) {
383                *dest = source.wrapping_add(*last);
384            }
385        }
386        3 => {
387            for i in 0..bwidth {
388                dest[i] = source[i].wrapping_add((last[i] as u32 / 2) as u8);
389            }
390            for i in bwidth..len {
391                dest[i] = source[i].wrapping_add(
392                    ((dest[i - bwidth] as u32).wrapping_add(last[i] as u32) / 2) as u8,
393                );
394            }
395        }
396        4 => {
397            for i in 0..bwidth {
398                dest[i] = source[i].wrapping_add(last[i]);
399            }
400            for i in bwidth..len {
401                dest[i] =
402                    source[i].wrapping_add(paeth(dest[i - bwidth], last[i], last[i - bwidth]));
403            }
404        }
405        _ => return None,
406    }
407    Some(())
408}
409
410fn normalize(
411    source: &[u8],
412    dest: &mut [u8],
413    depth: u8,
414    palette: bool,
415    width: usize,
416    trunc_16: bool,
417) -> Option<()> {
418    match depth {
419        16 => {
420            if trunc_16 {
421                for (i, d) in dest.iter_mut().enumerate() {
422                    *d = source[i * 2];
423                }
424            } else if IS_LITTLE_ENDIAN {
425                for (s, d) in source.chunks(2).zip(dest.chunks_mut(2)) {
426                    d[1] = s[0];
427                    d[0] = s[1];
428                }
429            } else {
430                dest.copy_from_slice(source);
431            }
432        }
433        8 => {
434            dest.copy_from_slice(source);
435        }
436        4 => {
437            let conv = if !palette { 17 } else { 1 };
438            for (i, d) in dest.get_mut(..width)?.iter_mut().enumerate() {
439                *d = (source[i / 2] >> (4 - i % 2 * 4) & 15) * conv;
440            }
441        }
442        2 => {
443            let conv = if !palette { 85 } else { 1 };
444            for (i, d) in dest.get_mut(..width)?.iter_mut().enumerate() {
445                *d = (source[i / 4] >> (6 - i % 4 * 2) & 3) * conv;
446            }
447        }
448        1 => {
449            let conv = if !palette { 255 } else { 1 };
450            for (i, d) in dest.get_mut(..width)?.iter_mut().enumerate() {
451                *d = (source[i / 8] >> (7 - i % 8) & 1) * conv;
452            }
453        }
454        _ => {}
455    }
456    Some(())
457}
458
459trait Emit {
460    fn emit(
461        state: &State,
462        source: &[u8],
463        image: &mut [u8],
464        x: usize,
465        y: usize,
466        width: usize,
467        inc: usize,
468        len: usize,
469    ) -> Option<()>;
470}
471
472struct EmitRgba8;
473
474impl Emit for EmitRgba8 {
475    fn emit(
476        state: &State,
477        source: &[u8],
478        image: &mut [u8],
479        x: usize,
480        y: usize,
481        width: usize,
482        inc: usize,
483        len: usize,
484    ) -> Option<()> {
485        use ColorType::*;
486        let src = source;
487        let mut out = y * width * 4 + x * 4;
488        let mut i = 0;
489        match state.header.color_type {
490            Indexed => {
491                let palette = state.palette;
492                let trans = state.trans;
493                let palette_len = palette.len();
494                let trans_len = trans.len();
495                for _ in 0..len {
496                    let t = src[i] as usize;
497                    let p = t * 3;
498                    if p + 2 >= palette_len {
499                        image[out] = 0;
500                        image[out + 1] = 0;
501                        image[out + 2] = 0;
502                    } else {
503                        image[out] = palette[p];
504                        image[out + 1] = palette[p + 1];
505                        image[out + 2] = palette[p + 2];
506                    }
507                    if t >= trans_len {
508                        image[out + 3] = 255;
509                    } else {
510                        image[out + 3] = trans[t];
511                    }
512                    i += 1;
513                    out += 4 * inc;
514                }
515            }
516            TrueColor => {
517                for _ in 0..len {
518                    image[out] = src[i];
519                    image[out + 1] = src[i + 1];
520                    image[out + 2] = src[i + 2];
521                    image[out + 3] = 255;
522                    i += 3;
523                    out += 4 * inc;
524                }
525            }
526            TrueColorAlpha => {
527                for _ in 0..len {
528                    image[out] = src[i];
529                    image[out + 1] = src[i + 1];
530                    image[out + 2] = src[i + 2];
531                    image[out + 3] = src[i + 3];
532                    i += 4;
533                    out += 4 * inc;
534                }
535            }
536            Greyscale => {
537                for c in src[..len].iter().copied() {
538                    image[out] = c;
539                    image[out + 1] = c;
540                    image[out + 2] = c;
541                    image[out + 3] = 255;
542                    out += 4 * inc;
543                }
544            }
545            GreyscaleAlpha => {
546                let mut i = 0;
547                for _ in 0..len {
548                    let c = src[i];
549                    image[out] = c;
550                    image[out + 1] = c;
551                    image[out + 2] = c;
552                    image[out + 3] = src[i + 1];
553                    i += 2;
554                    out += 4 * inc;
555                }
556            }
557        }
558        Some(())
559    }
560}
561
562#[inline(always)]
563fn paeth(a: u8, b: u8, c: u8) -> u8 {
564    let pa = ((b as i32).wrapping_sub(c as i32)).abs();
565    let pb = ((a as i32).wrapping_sub(c as i32)).abs();
566    let pc = ((a as i32)
567        .wrapping_add(b as i32)
568        .wrapping_sub(c as i32)
569        .wrapping_sub(c as i32))
570    .abs();
571    if pc < pa && pc < pb {
572        c
573    } else if pb < pa {
574        b
575    } else {
576        a
577    }
578}
579
580fn get_u32be(buf: &[u8], offset: usize) -> u32 {
581    (buf[offset] as u32) << 24
582        | (buf[offset + 1] as u32) << 16
583        | (buf[offset + 2] as u32) << 8
584        | buf[offset + 3] as u32
585}
586
587const fn chunk_name(bytes: &[u8; 4]) -> u32 {
588    (bytes[0] as u32) << 24 | (bytes[1] as u32) << 16 | (bytes[2] as u32) << 8 | bytes[3] as u32
589}