icu_lib 0.11.0

Image Converter Ultra Library
Documentation
use crate::endecoder::lvgl::color_converter::{extract_indexed, rgba8888_from, rgba8888_to};
use crate::endecoder::lvgl::{
    has_flag, with_flag, Compress, CompressedImage, Flags, HeaderFlag, ImageDescriptor,
    ImageHeader, LVGLVersion, LVGL,
};
use crate::endecoder::{EnDecoder, ImageInfo};
use crate::midata::{IndexedImageData, MiData};
use crate::EncoderParams;
use image::imageops;
use image::RgbaImage;
use serde_json::{json, Value};
use std::io::{Cursor, Write};

impl EnDecoder for LVGL {
    fn can_decode(&self, data: &[u8]) -> bool {
        ImageHeader::parse(data).is_some()
    }

    fn encode(&self, data: &MiData, encoder_params: EncoderParams) -> Vec<u8> {
        let color_format: crate::endecoder::lvgl::ColorFormat = encoder_params.color_format.into();
        if color_format == crate::endecoder::lvgl::ColorFormat::UNKNOWN {
            return Vec::new();
        }

        let img = match data {
            MiData::RGBA(img) => img,
            MiData::INDEXED(indexed) => &indexed.rgba,
            _ => return Vec::new(),
        };

        let stride = color_format.get_stride_size(img.width(), encoder_params.stride_align);
        let mut img_data = img.clone();

        if let Some(dither) = encoder_params.dither {
            let cmap = color_quant::NeuQuant::new(dither as i32, 256, img_data.as_mut());
            imageops::dither(&mut img_data, &cmap);
        }

        let mut img_data = rgba8888_to(
            img_data.as_mut(),
            color_format,
            img.width(),
            img.height(),
            stride,
            encoder_params.dither,
        );

        let mut flags = Flags::from(0u16);

        if encoder_params.compress != Compress::NONE {
            if encoder_params.compress == Compress::LZ4
                && encoder_params.lvgl_version != LVGLVersion::V9
            {
                log::error!("LVGL LZ4 compression is only supported for v9 images");
                return vec![];
            }
            let block_size = ((color_format.get_bpp() + 7) >> 3) as usize;
            let Some(compressed) =
                CompressedImage::encode(encoder_params.compress, &img_data, block_size)
            else {
                log::error!("Failed to compress LVGL image data");
                return vec![];
            };
            img_data = compressed;
            flags = with_flag(flags, HeaderFlag::COMPRESSED);
        }

        let mut buf = Cursor::new(Vec::new());
        buf.write_all(
            &ImageDescriptor::new(
                ImageHeader::new(
                    encoder_params.lvgl_version,
                    color_format,
                    flags,
                    img.width() as u16,
                    img.height() as u16,
                    stride as u16,
                ),
                img_data,
            )
            .encode(),
        )
        .unwrap();

        buf.into_inner()
    }

    fn decode(&self, data: Vec<u8>) -> MiData {
        log::trace!("Decoding image with data size: {}", data.len());
        let img_desc = ImageDescriptor::decode(data);

        if img_desc.data_size == 0 {
            return MiData::RGBA(RgbaImage::new(0, 0));
        }

        let header = &img_desc.header;

        log::trace!("Decoding image with color format: {:?}", header.cf());
        log::trace!("Decoded image header: {:#?}", img_desc.header);
        log::trace!("Converting image data to RGBA");

        let cf = header.cf();
        let w = header.w() as u32;
        let h = header.h() as u32;
        let stride = header.stride() as u32;

        if let Some((palette, indexes, bpp)) = extract_indexed(&img_desc.data, cf, w, h, stride) {
            let rgba_buf = rgba8888_from(&img_desc.data, cf, w, h, stride);
            let rgba = RgbaImage::from_vec(w, h, rgba_buf).unwrap_or_else(|| RgbaImage::new(0, 0));
            return MiData::INDEXED(IndexedImageData {
                rgba,
                palette,
                indexes,
                bpp,
                width: w,
                height: h,
            });
        }

        let img_buffer =
            RgbaImage::from_vec(w, h, rgba8888_from(&img_desc.data, cf, w, h, stride)).unwrap();

        log::trace!("Converted image data to RGBA");
        log::trace!(
            "Decoded image with size: {}x{}",
            img_buffer.width(),
            img_buffer.height()
        );
        log::trace!("Creating MiData object with RGBA image data and returning it");

        MiData::RGBA(img_buffer)
    }

    fn info(&self, data: &[u8]) -> ImageInfo {
        let Some((header, payload)) = ImageHeader::split(data) else {
            return ImageInfo {
                width: 0,
                height: 0,
                data_size: data.len() as u32,
                format: "LVGL.Unknown(UNKNOWN)".to_string(),
                other_info: Value::Null,
            };
        };

        let mut other_info = serde_json::Map::new();

        other_info.insert(
            "LVGL Version".to_string(),
            Value::from(format!("{:#?}", header.version())),
        );
        other_info.insert(
            "Color Format".to_string(),
            Value::from(format!("{:#?}", header.cf())),
        );
        other_info.insert(
            "Flags".to_string(),
            Value::from(format!("{:#?}", header.flags())),
        );
        if header.version() == LVGLVersion::V9 {
            other_info.insert("Stride".to_string(), Value::from(header.stride()));
        }

        if has_flag(header.flags(), HeaderFlag::COMPRESSED) {
            if let Some(compressed) = header.expected_data_size().and_then(|expected| {
                let block_size = ((header.cf().get_bpp() + 7) >> 3) as usize;
                CompressedImage::parse(payload, expected, block_size)
            }) {
                let (method, compressed_size, decompressed_size) = compressed.info();
                other_info.insert(
                    "Compressed Info".to_owned(),
                    json!({
                        "Method": format!("{method:#?}"),
                        "Size": compressed_size,
                        "Decompressed Size": decompressed_size
                    }),
                );
            }
        }

        ImageInfo {
            width: header.w() as u32,
            height: header.h() as u32,
            data_size: data.len() as u32,
            format: format!("LVGL.{:?}({:?})", header.version(), header.cf()),
            other_info: Value::from(other_info),
        }
    }
}