msg_tool\utils/
img.rs

1//! Image Utilities
2#[cfg(feature = "image-jxl")]
3use super::jxl::*;
4use crate::ext::io::*;
5use crate::types::*;
6use anyhow::Result;
7use std::convert::TryFrom;
8use std::io::Write;
9
10/// Reverses the alpha values of an image.
11///
12/// Only supports RGBA or BGRA images with 8-bit depth.
13pub fn reverse_alpha_values(data: &mut ImageData) -> Result<()> {
14    if data.color_type != ImageColorType::Rgba && data.color_type != ImageColorType::Bgra {
15        return Err(anyhow::anyhow!("Image is not RGBA or BGRA"));
16    }
17    if data.depth != 8 {
18        return Err(anyhow::anyhow!(
19            "Alpha value reversal only supports 8-bit depth"
20        ));
21    }
22    for i in (0..data.data.len()).step_by(4) {
23        data.data[i + 3] = 255 - data.data[i + 3];
24    }
25    Ok(())
26}
27
28/// Converts a BGR image to BGRA format.
29///
30/// Only supports BGR images with 8-bit depth.
31pub fn convert_bgr_to_bgra(data: &mut ImageData) -> Result<()> {
32    if data.color_type != ImageColorType::Bgr {
33        return Err(anyhow::anyhow!("Image is not BGR"));
34    }
35    if data.depth != 8 {
36        return Err(anyhow::anyhow!(
37            "BGR to BGRA conversion only supports 8-bit depth"
38        ));
39    }
40    let mut new_data = Vec::with_capacity(data.data.len() / 3 * 4);
41    for chunk in data.data.chunks_exact(3) {
42        new_data.push(chunk[0]); // B
43        new_data.push(chunk[1]); // G
44        new_data.push(chunk[2]); // R
45        new_data.push(255); // A
46    }
47    data.data = new_data;
48    data.color_type = ImageColorType::Bgra;
49    Ok(())
50}
51
52/// Converts a BGR image to RGB format.
53///
54/// Only supports BGR images with 8-bit depth.
55pub fn convert_bgr_to_rgb(data: &mut ImageData) -> Result<()> {
56    if data.color_type != ImageColorType::Bgr {
57        return Err(anyhow::anyhow!("Image is not BGR"));
58    }
59    if data.depth != 8 {
60        return Err(anyhow::anyhow!(
61            "BGR to RGB conversion only supports 8-bit depth"
62        ));
63    }
64    for i in (0..data.data.len()).step_by(3) {
65        let b = data.data[i];
66        data.data[i] = data.data[i + 2];
67        data.data[i + 2] = b;
68    }
69    data.color_type = ImageColorType::Rgb;
70    Ok(())
71}
72
73/// Converts a BGRA image to BGR format.
74///
75/// Only supports BGRA images with 8-bit depth.
76pub fn convert_bgra_to_bgr(data: &mut ImageData) -> Result<()> {
77    if data.color_type != ImageColorType::Bgra {
78        return Err(anyhow::anyhow!("Image is not BGRA"));
79    }
80    if data.depth != 8 {
81        return Err(anyhow::anyhow!(
82            "BGRA to BGR conversion only supports 8-bit depth"
83        ));
84    }
85    let mut new_data = Vec::with_capacity(data.data.len() / 4 * 3);
86    for chunk in data.data.chunks_exact(4) {
87        new_data.push(chunk[0]); // B
88        new_data.push(chunk[1]); // G
89        new_data.push(chunk[2]); // R
90    }
91    data.data = new_data;
92    data.color_type = ImageColorType::Bgr;
93    Ok(())
94}
95
96/// Converts a BGRA image to RGBA format.
97///
98/// Only supports BGRA images with 8-bit depth.
99pub fn convert_bgra_to_rgba(data: &mut ImageData) -> Result<()> {
100    if data.color_type != ImageColorType::Bgra {
101        return Err(anyhow::anyhow!("Image is not BGRA"));
102    }
103    if data.depth != 8 {
104        return Err(anyhow::anyhow!(
105            "BGRA to RGBA conversion only supports 8-bit depth"
106        ));
107    }
108    for i in (0..data.data.len()).step_by(4) {
109        let b = data.data[i];
110        data.data[i] = data.data[i + 2];
111        data.data[i + 2] = b;
112    }
113    data.color_type = ImageColorType::Rgba;
114    Ok(())
115}
116
117/// Converts an RGB image to RGBA format.
118///
119/// Only supports RGB images with 8-bit depth.
120pub fn convert_rgb_to_rgba(data: &mut ImageData) -> Result<()> {
121    if data.color_type != ImageColorType::Rgb {
122        return Err(anyhow::anyhow!("Image is not RGB"));
123    }
124    if data.depth != 8 {
125        return Err(anyhow::anyhow!(
126            "RGB to RGBA conversion only supports 8-bit depth"
127        ));
128    }
129    let mut new_data = Vec::with_capacity(data.data.len() / 3 * 4);
130    for chunk in data.data.chunks_exact(3) {
131        new_data.push(chunk[0]); // R
132        new_data.push(chunk[1]); // G
133        new_data.push(chunk[2]); // B
134        new_data.push(255); // A
135    }
136    data.data = new_data;
137    data.color_type = ImageColorType::Rgba;
138    Ok(())
139}
140
141/// Converts an RGB image to BGR format.
142///
143/// Only supports RGB images with 8-bit depth.
144pub fn convert_rgb_to_bgr(data: &mut ImageData) -> Result<()> {
145    if data.color_type != ImageColorType::Rgb {
146        return Err(anyhow::anyhow!("Image is not RGB"));
147    }
148    if data.depth != 8 {
149        return Err(anyhow::anyhow!(
150            "RGB to BGR conversion only supports 8-bit depth"
151        ));
152    }
153    for i in (0..data.data.len()).step_by(3) {
154        let r = data.data[i];
155        data.data[i] = data.data[i + 2];
156        data.data[i + 2] = r;
157    }
158    data.color_type = ImageColorType::Bgr;
159    Ok(())
160}
161
162/// Converts an RGBA image to BGRA format.
163///
164/// Only supports RGBA images with 8-bit depth.
165pub fn convert_rgba_to_bgra(data: &mut ImageData) -> Result<()> {
166    if data.color_type != ImageColorType::Rgba {
167        return Err(anyhow::anyhow!("Image is not RGBA"));
168    }
169    if data.depth != 8 {
170        return Err(anyhow::anyhow!(
171            "RGBA to BGRA conversion only supports 8-bit depth"
172        ));
173    }
174    for i in (0..data.data.len()).step_by(4) {
175        let r = data.data[i];
176        data.data[i] = data.data[i + 2];
177        data.data[i + 2] = r;
178    }
179    data.color_type = ImageColorType::Bgra;
180    Ok(())
181}
182
183/// Converts a Grayscale image to RGB format.
184pub fn convert_grayscale_to_rgb(data: &mut ImageData) -> Result<()> {
185    if data.color_type != ImageColorType::Grayscale {
186        return Err(anyhow::anyhow!("Image is not Grayscale"));
187    }
188    if data.depth != 8 {
189        return Err(anyhow::anyhow!(
190            "Grayscale to RGB conversion only supports 8-bit depth"
191        ));
192    }
193    let mut new_data = Vec::with_capacity(data.data.len() * 3);
194    for &gray in &data.data {
195        new_data.push(gray); // R
196        new_data.push(gray); // G
197        new_data.push(gray); // B
198    }
199    data.data = new_data;
200    data.color_type = ImageColorType::Rgb;
201    Ok(())
202}
203
204/// Converts a Grayscale image to RGBA format.
205pub fn convert_grayscale_to_rgba(data: &mut ImageData) -> Result<()> {
206    if data.color_type != ImageColorType::Grayscale {
207        return Err(anyhow::anyhow!("Image is not Grayscale"));
208    }
209    if data.depth != 8 {
210        return Err(anyhow::anyhow!(
211            "Grayscale to RGBA conversion only supports 8-bit depth"
212        ));
213    }
214    let mut new_data = Vec::with_capacity(data.data.len() * 4);
215    for &gray in &data.data {
216        new_data.push(gray); // R
217        new_data.push(gray); // G
218        new_data.push(gray); // B
219        new_data.push(255); // A
220    }
221    data.data = new_data;
222    data.color_type = ImageColorType::Rgba;
223    Ok(())
224}
225
226/// Converts an image to RGBA format.
227pub fn convert_to_rgba(data: &mut ImageData) -> Result<()> {
228    match data.color_type {
229        ImageColorType::Rgb => convert_rgb_to_rgba(data),
230        ImageColorType::Bgr => convert_bgr_to_bgra(data),
231        ImageColorType::Rgba => Ok(()),
232        ImageColorType::Bgra => convert_bgra_to_rgba(data),
233        ImageColorType::Grayscale => convert_grayscale_to_rgba(data),
234    }
235}
236
237/// Encodes an image to the specified format and writes it to a file.
238///
239/// * `data` - The image data to encode.
240/// * `typ` - The output image format.
241/// * `filename` - The path of the file to write the encoded image to.
242/// * `config` - Extra configuration.
243pub fn encode_img(
244    data: ImageData,
245    typ: ImageOutputType,
246    filename: &str,
247    config: &ExtraConfig,
248) -> Result<()> {
249    let mut file = crate::utils::files::write_file(filename)?;
250    encode_img_writer(data, typ, &mut file, config)
251}
252
253/// Encodes an image to the specified format and writes it to a writer.
254///
255/// * `data` - The image data to encode.
256/// * `typ` - The output image format.
257/// * `filename` - The path of the file to write the encoded image to.
258/// * `config` - Extra configuration.
259pub fn encode_img_writer<T: Write>(
260    mut data: ImageData,
261    typ: ImageOutputType,
262    mut file: &mut T,
263    config: &ExtraConfig,
264) -> Result<()> {
265    match typ {
266        ImageOutputType::Png => {
267            let color_type = match data.color_type {
268                ImageColorType::Grayscale => png::ColorType::Grayscale,
269                ImageColorType::Rgb => png::ColorType::Rgb,
270                ImageColorType::Rgba => png::ColorType::Rgba,
271                ImageColorType::Bgr => {
272                    convert_bgr_to_rgb(&mut data)?;
273                    png::ColorType::Rgb
274                }
275                ImageColorType::Bgra => {
276                    convert_bgra_to_rgba(&mut data)?;
277                    png::ColorType::Rgba
278                }
279            };
280            let bit_depth = match &data.depth {
281                1 => png::BitDepth::One,
282                2 => png::BitDepth::Two,
283                4 => png::BitDepth::Four,
284                8 => png::BitDepth::Eight,
285                16 => png::BitDepth::Sixteen,
286                _ => return Err(anyhow::anyhow!("Unsupported bit depth: {}", data.depth)),
287            };
288            let mut encoder = png::Encoder::new(&mut file, data.width, data.height);
289            encoder.set_color(color_type);
290            encoder.set_depth(bit_depth);
291            encoder.set_compression(config.png_compression_level.to_compression());
292            let mut writer = encoder.write_header()?;
293            writer.write_image_data(&data.data)?;
294            writer.finish()?;
295            Ok(())
296        }
297        #[cfg(feature = "image-jpg")]
298        ImageOutputType::Jpg => {
299            let color_type = match data.color_type {
300                ImageColorType::Grayscale => mozjpeg::ColorSpace::JCS_GRAYSCALE,
301                ImageColorType::Rgb => mozjpeg::ColorSpace::JCS_RGB,
302                ImageColorType::Rgba => mozjpeg::ColorSpace::JCS_EXT_RGBA,
303                ImageColorType::Bgr => {
304                    convert_bgr_to_rgb(&mut data)?;
305                    mozjpeg::ColorSpace::JCS_RGB
306                }
307                ImageColorType::Bgra => {
308                    convert_bgra_to_rgba(&mut data)?;
309                    mozjpeg::ColorSpace::JCS_EXT_RGBA
310                }
311            };
312            if data.depth != 8 {
313                return Err(anyhow::anyhow!(
314                    "JPEG encoding only supports 8-bit depth, found: {}",
315                    data.depth
316                ));
317            }
318            let mut encoder = mozjpeg::compress::Compress::new(color_type);
319            encoder.set_size(data.width as usize, data.height as usize);
320            encoder.set_quality(config.jpeg_quality as f32);
321            let mut start = encoder.start_compress(file)?;
322            start.write_scanlines(&data.data)?;
323            start.finish()?;
324            Ok(())
325        }
326        #[cfg(feature = "image-webp")]
327        ImageOutputType::Webp => {
328            let color_type = match data.color_type {
329                ImageColorType::Rgb => webp::PixelLayout::Rgb,
330                ImageColorType::Rgba => webp::PixelLayout::Rgba,
331                ImageColorType::Bgr => {
332                    convert_bgr_to_rgb(&mut data)?;
333                    webp::PixelLayout::Rgb
334                }
335                ImageColorType::Bgra => {
336                    convert_bgra_to_rgba(&mut data)?;
337                    webp::PixelLayout::Rgba
338                }
339                _ => {
340                    return Err(anyhow::anyhow!(
341                        "Unsupported color type for WebP: {:?}",
342                        data.color_type
343                    ));
344                }
345            };
346            if data.depth != 8 {
347                return Err(anyhow::anyhow!(
348                    "WebP encoding only supports 8-bit depth, found: {}",
349                    data.depth
350                ));
351            }
352            let encoder = webp::Encoder::new(&data.data, color_type, data.width, data.height);
353            let re = encoder
354                .encode_simple(config.webp_lossless, config.webp_quality as f32)
355                .map_err(|e| anyhow::anyhow!("Failed to encode WebP image: {:?}", e))?;
356            file.write_all(&re)?;
357            Ok(())
358        }
359        #[cfg(feature = "image-jxl")]
360        ImageOutputType::Jxl => {
361            let data = encode_jxl(data, config)?;
362            file.write_all(&data)?;
363            Ok(())
364        }
365    }
366}
367
368pub fn convert_grayscale_alpha_to_rgba(
369    raw_data: &[u8],
370    width: usize,
371    height: usize,
372    bit_depth: u8,
373) -> Result<ImageData> {
374    if bit_depth != 8 && bit_depth != 16 {
375        return Err(anyhow::anyhow!(
376            "Unsupported bit depth for GrayscaleAlpha to RGBA conversion: {}",
377            bit_depth
378        ));
379    }
380    let bytes_per_channel = (bit_depth / 8) as usize;
381    let stride = width * 2 * bytes_per_channel;
382    if raw_data.len() != stride * height {
383        return Err(anyhow::anyhow!(
384            "Input data size does not match expected size for GrayscaleAlpha image"
385        ));
386    }
387    let mut data = Vec::with_capacity(width * height * 4 * bytes_per_channel);
388    for y in 0..height {
389        for x in 0..width {
390            let base = y * stride + x * 2 * bytes_per_channel;
391            // Grayscale channel
392            data.extend_from_slice(&raw_data[base..base + bytes_per_channel]);
393            data.extend_from_slice(&raw_data[base..base + bytes_per_channel]);
394            data.extend_from_slice(&raw_data[base..base + bytes_per_channel]);
395            // Alpha channel
396            data.extend_from_slice(
397                &raw_data[base + bytes_per_channel..base + 2 * bytes_per_channel],
398            );
399        }
400    }
401    Ok(ImageData {
402        width: width as u32,
403        height: height as u32,
404        depth: bit_depth,
405        color_type: ImageColorType::Rgba,
406        data,
407    })
408}
409
410/// Loads a PNG image from the given reader and returns its data.
411pub fn load_png<R: std::io::Read + std::io::Seek>(data: R) -> Result<ImageData> {
412    let decoder = png::Decoder::new(std::io::BufReader::new(data));
413    let mut reader = decoder.read_info()?;
414    let bit_depth = match reader.info().bit_depth {
415        png::BitDepth::One => 1,
416        png::BitDepth::Two => 2,
417        png::BitDepth::Four => 4,
418        png::BitDepth::Eight => 8,
419        png::BitDepth::Sixteen => 16,
420    };
421    let color_type = match reader.info().color_type {
422        png::ColorType::Grayscale => ImageColorType::Grayscale,
423        png::ColorType::Rgb => ImageColorType::Rgb,
424        png::ColorType::Rgba => ImageColorType::Rgba,
425        png::ColorType::GrayscaleAlpha => ImageColorType::Rgba,
426        png::ColorType::Indexed => {
427            if let Some(palette) = &reader.info().palette {
428                if palette.len() % 3 != 0 {
429                    return Err(anyhow::anyhow!(
430                        "Invalid PNG palette length: {}",
431                        palette.len()
432                    ));
433                }
434                ImageColorType::Rgb
435            } else {
436                return Err(anyhow::anyhow!(
437                    "PNG image has indexed color type but no palette"
438                ));
439            }
440        }
441    };
442    if reader.info().color_type == png::ColorType::GrayscaleAlpha {
443        let height = reader.info().height as usize;
444        let width = reader.info().width as usize;
445        let raw_stride = width * 2 * bit_depth as usize / 8;
446        let mut raw_data = vec![0; raw_stride * height];
447        reader.next_frame(&mut raw_data)?;
448        return convert_grayscale_alpha_to_rgba(&raw_data, width, height, bit_depth);
449    }
450    if reader.info().color_type == png::ColorType::Indexed {
451        let mut palette = reader
452            .info()
453            .palette
454            .as_ref()
455            .ok_or_else(|| anyhow::anyhow!("PNG image has indexed color type but no palette"))?
456            .to_vec();
457        let mut palette_format = PaletteFormat::Rgb;
458        if let Some(trns) = &reader.info().trns {
459            let mut new_palette = Vec::with_capacity(palette.len() / 3 * 4);
460            let trns_len = trns.len();
461            for i in 0..(palette.len() / 3) {
462                new_palette.push(palette[i * 3]);
463                new_palette.push(palette[i * 3 + 1]);
464                new_palette.push(palette[i * 3 + 2]);
465                let alpha = if i < trns_len { trns[i] } else { 255 };
466                new_palette.push(alpha);
467            }
468            palette = new_palette;
469            palette_format = PaletteFormat::RgbA;
470        }
471        let width = reader.info().width as usize;
472        let height = reader.info().height as usize;
473        let raw_stride = width * bit_depth as usize / 8;
474        let mut raw_data = vec![0; raw_stride * height];
475        reader.next_frame(&mut raw_data)?;
476        return convert_index_palette_to_normal_bitmap(
477            &raw_data,
478            bit_depth as usize,
479            &palette,
480            palette_format,
481            width,
482            height,
483        );
484    }
485    let stride = reader.info().width as usize * color_type.bpp(bit_depth) as usize / 8;
486    let mut data = vec![0; stride * reader.info().height as usize];
487    reader.next_frame(&mut data)?;
488    Ok(ImageData {
489        width: reader.info().width,
490        height: reader.info().height,
491        depth: bit_depth,
492        color_type,
493        data,
494    })
495}
496
497#[cfg(feature = "mozjpeg")]
498pub fn load_jpg<R: std::io::Read>(data: R) -> Result<ImageData> {
499    let decoder = mozjpeg::decompress::Decompress::new_reader(std::io::BufReader::new(data))?;
500    let color_type = match decoder.color_space() {
501        mozjpeg::ColorSpace::JCS_GRAYSCALE => ImageColorType::Grayscale,
502        mozjpeg::ColorSpace::JCS_RGB => ImageColorType::Rgb,
503        mozjpeg::ColorSpace::JCS_EXT_RGBA => ImageColorType::Rgba,
504        _ => ImageColorType::Rgb, // Convert other types to RGB
505    };
506    let width = decoder.width() as u32;
507    let height = decoder.height() as u32;
508    let stride = width as usize * color_type.bpp(8) as usize / 8;
509    let mut data = vec![0; stride * height as usize];
510    let mut re = match color_type {
511        ImageColorType::Grayscale => decoder.grayscale()?,
512        ImageColorType::Rgb => decoder.rgb()?,
513        ImageColorType::Rgba => decoder.rgba()?,
514        _ => {
515            unreachable!(); // We already checked the color type above
516        }
517    };
518    re.read_scanlines_into(&mut data)?;
519    Ok(ImageData {
520        width,
521        height,
522        depth: 8,
523        color_type,
524        data,
525    })
526}
527
528/// Decodes an image from the specified file path and returns its data.
529///
530/// * `typ` - The type of the image to decode.
531/// * `filename` - The path of the file to decode.
532pub fn decode_img(typ: ImageOutputType, filename: &str) -> Result<ImageData> {
533    match typ {
534        ImageOutputType::Png => {
535            let file = crate::utils::files::read_file(filename)?;
536            let reader = MemReader::new(file);
537            load_png(reader)
538        }
539        #[cfg(feature = "image-jpg")]
540        ImageOutputType::Jpg => {
541            let file = crate::utils::files::read_file(filename)?;
542            load_jpg(&file[..])
543        }
544        #[cfg(feature = "image-webp")]
545        ImageOutputType::Webp => {
546            let file = crate::utils::files::read_file(filename)?;
547            let decoder = webp::Decoder::new(&file);
548            let image = decoder
549                .decode()
550                .ok_or(anyhow::anyhow!("Failed to decode WebP image"))?;
551            let color_type = if image.is_alpha() {
552                ImageColorType::Rgba
553            } else {
554                ImageColorType::Rgb
555            };
556            let width = image.width();
557            let height = image.height();
558            let stride = width as usize * color_type.bpp(8) as usize / 8;
559            let mut data = vec![0; stride * height as usize];
560            if image.len() != data.len() {
561                return Err(anyhow::anyhow!(
562                    "WebP image data size mismatch: expected {}, got {}",
563                    data.len(),
564                    image.len()
565                ));
566            }
567            data.copy_from_slice(&image);
568            Ok(ImageData {
569                width,
570                height,
571                depth: 8,
572                color_type,
573                data,
574            })
575        }
576        #[cfg(feature = "image-jxl")]
577        ImageOutputType::Jxl => {
578            let file = crate::utils::files::read_file(filename)?;
579            decode_jxl(&file[..])
580        }
581    }
582}
583
584/// Draws an image on a canvas with specified offsets.
585///
586/// * `img` - The image data to draw.
587/// * `canvas_width` - The width of the canvas.
588/// * `canvas_height` - The height of the canvas.
589/// * `offset_x` - The horizontal offset to start drawing the image.
590/// * `offset_y` - The vertical offset to start drawing the image.
591///
592/// Returns the canvas image data.
593pub fn draw_on_canvas(
594    img: ImageData,
595    canvas_width: u32,
596    canvas_height: u32,
597    offset_x: u32,
598    offset_y: u32,
599) -> Result<ImageData> {
600    let bytes_per_pixel = img.color_type.bpp(img.depth) as u32 / 8;
601    let mut canvas_data = vec![0u8; (canvas_width * canvas_height * bytes_per_pixel) as usize];
602    let canvas_stride = canvas_width * bytes_per_pixel;
603    let img_stride = img.width * bytes_per_pixel;
604
605    for y in 0..img.height {
606        let canvas_y = y + offset_y;
607        if canvas_y >= canvas_height {
608            continue;
609        }
610        let canvas_start = (canvas_y * canvas_stride + offset_x * bytes_per_pixel) as usize;
611        let img_start = (y * img_stride) as usize;
612        let copy_len = img_stride as usize;
613        if canvas_start + copy_len > canvas_data.len() {
614            continue;
615        }
616        canvas_data[canvas_start..canvas_start + copy_len]
617            .copy_from_slice(&img.data[img_start..img_start + copy_len]);
618    }
619
620    Ok(ImageData {
621        width: canvas_width,
622        height: canvas_height,
623        color_type: img.color_type,
624        depth: img.depth,
625        data: canvas_data,
626    })
627}
628
629/// Flips an image vertically.
630pub fn flip_image(data: &mut ImageData) -> Result<()> {
631    if data.height <= 1 {
632        return Ok(());
633    }
634    let row_size = data.color_type.bpp(data.depth) as usize * data.width as usize / 8;
635    if row_size == 0 {
636        return Ok(());
637    }
638
639    let mut i = 0;
640    let mut j = data.height as usize - 1;
641    while i < j {
642        let (top, bottom) = data.data.split_at_mut(j * row_size);
643        let top_row = &mut top[i * row_size..i * row_size + row_size];
644        let bottom_row = &mut bottom[0..row_size];
645        top_row.swap_with_slice(bottom_row);
646        i += 1;
647        j -= 1;
648    }
649
650    Ok(())
651}
652
653/// Applies opacity to an image.
654///
655/// Only supports RGBA or BGRA images with 8-bit depth.
656pub fn apply_opacity(img: &mut ImageData, opacity: u8) -> Result<()> {
657    if img.color_type != ImageColorType::Rgba && img.color_type != ImageColorType::Bgra {
658        return Err(anyhow::anyhow!("Image is not RGBA or BGRA"));
659    }
660    if img.depth != 8 {
661        return Err(anyhow::anyhow!(
662            "Opacity application only supports 8-bit depth"
663        ));
664    }
665    for i in (0..img.data.len()).step_by(4) {
666        img.data[i + 3] = (img.data[i + 3] as u16 * opacity as u16 / 255) as u8;
667    }
668    Ok(())
669}
670
671/// Draws an image on another image. The pixel data of `diff` will completely overwrite the pixel data of `base`.
672///
673/// * `base` - The base image to draw on.
674/// * `diff` - The image to draw.
675/// * `left` - The horizontal offset to start drawing the image.
676/// * `top` - The vertical offset to start drawing the image.
677pub fn draw_on_image(base: &mut ImageData, diff: &ImageData, left: u32, top: u32) -> Result<()> {
678    if base.color_type != diff.color_type {
679        return Err(anyhow::anyhow!("Image color types do not match"));
680    }
681    if base.depth != diff.depth {
682        return Err(anyhow::anyhow!("Image depths do not match"));
683    }
684
685    let bits_per_pixel = base.color_type.bpp(base.depth) as usize;
686    if bits_per_pixel == 0 || bits_per_pixel % 8 != 0 {
687        return Err(anyhow::anyhow!(
688            "Unsupported pixel bit layout: {} bits",
689            bits_per_pixel
690        ));
691    }
692    let bpp = bits_per_pixel / 8;
693
694    let base_stride = base.width as usize * bpp;
695    let diff_stride = diff.width as usize * bpp;
696
697    for y in 0..diff.height {
698        let base_y = top + y;
699        if base_y >= base.height {
700            continue;
701        }
702
703        for x in 0..diff.width {
704            let base_x = left + x;
705            if base_x >= base.width {
706                continue;
707            }
708
709            let diff_idx = (y as usize * diff_stride) + (x as usize * bpp);
710            let base_idx = (base_y as usize * base_stride) + (base_x as usize * bpp);
711
712            // safety: bounds should hold given width/height checks, but guard to avoid panics
713            if diff_idx + bpp > diff.data.len() || base_idx + bpp > base.data.len() {
714                continue;
715            }
716
717            base.data[base_idx..base_idx + bpp]
718                .copy_from_slice(&diff.data[diff_idx..diff_idx + bpp]);
719        }
720    }
721
722    Ok(())
723}
724
725/// Draws an image on another image with specified opacity.
726///
727/// * `base` - The base image to draw on.
728/// * `diff` - The image to draw with opacity.
729/// * `left` - The horizontal offset to start drawing the image.
730/// * `top` - The vertical offset to start drawing the image.
731/// * `opacity` - The opacity level to apply to the drawn image (0-255
732pub fn draw_on_img_with_opacity(
733    base: &mut ImageData,
734    diff: &ImageData,
735    left: u32,
736    top: u32,
737    opacity: u8,
738) -> Result<()> {
739    if base.color_type != diff.color_type {
740        return Err(anyhow::anyhow!("Image color types do not match"));
741    }
742    if base.color_type != ImageColorType::Rgba && base.color_type != ImageColorType::Bgra {
743        return Err(anyhow::anyhow!("Images are not RGBA or BGRA"));
744    }
745    if base.depth != 8 || diff.depth != 8 {
746        return Err(anyhow::anyhow!(
747            "Image drawing with opacity only supports 8-bit depth"
748        ));
749    }
750
751    let bpp = 4;
752    let base_stride = base.width as usize * bpp;
753    let diff_stride = diff.width as usize * bpp;
754
755    for y in 0..diff.height {
756        let base_y = top + y;
757        if base_y >= base.height {
758            continue;
759        }
760
761        for x in 0..diff.width {
762            let base_x = left + x;
763            if base_x >= base.width {
764                continue;
765            }
766
767            let diff_idx = (y as usize * diff_stride) + (x as usize * bpp);
768            let base_idx = (base_y as usize * base_stride) + (base_x as usize * bpp);
769
770            let diff_pixel = &diff.data[diff_idx..diff_idx + bpp];
771            let base_pixel_orig = base.data[base_idx..base_idx + bpp].to_vec();
772
773            let src_alpha_u16 = (diff_pixel[3] as u16 * opacity as u16) / 255;
774
775            if src_alpha_u16 == 0 {
776                continue;
777            }
778
779            let dst_alpha_u16 = base_pixel_orig[3] as u16;
780
781            // out_alpha = src_alpha + dst_alpha * (1 - src_alpha)
782            let out_alpha_u16 = src_alpha_u16 + (dst_alpha_u16 * (255 - src_alpha_u16)) / 255;
783
784            if out_alpha_u16 == 0 {
785                for i in 0..4 {
786                    base.data[base_idx + i] = 0;
787                }
788                continue;
789            }
790
791            // out_color = (src_color * src_alpha + dst_color * dst_alpha * (1 - src_alpha)) / out_alpha
792            for i in 0..3 {
793                let src_comp = diff_pixel[i] as u16;
794                let dst_comp = base_pixel_orig[i] as u16;
795
796                let numerator = src_comp as u32 * src_alpha_u16 as u32
797                    + (dst_comp as u32 * dst_alpha_u16 as u32 * (255 - src_alpha_u16) as u32) / 255;
798                base.data[base_idx + i] = (numerator / out_alpha_u16 as u32) as u8;
799            }
800            base.data[base_idx + 3] = out_alpha_u16 as u8;
801        }
802    }
803
804    Ok(())
805}
806
807#[derive(Debug, Clone, Copy, PartialEq, Eq)]
808pub enum PaletteFormat {
809    /// R G B Color
810    Rgb,
811    /// B G R Color
812    Bgr,
813    /// R G B Color with a unused byte
814    RgbX,
815    /// B G R Color with a unused byte
816    BgrX,
817    /// R G B A Color
818    RgbA,
819    /// B G R A Color
820    BgrA,
821}
822
823/// Converts indexed pixel data and a palette into a standard image buffer.
824///
825/// `pixel_size` is expressed in bits per pixel for the indexed data.
826pub fn convert_index_palette_to_normal_bitmap(
827    pixel_data: &[u8],
828    pixel_size: usize,
829    palettes: &[u8],
830    palette_format: PaletteFormat,
831    width: usize,
832    height: usize,
833) -> Result<ImageData> {
834    if width == 0 || height == 0 {
835        return Err(anyhow::anyhow!("Image dimensions must be non-zero"));
836    }
837    if pixel_size == 0 {
838        return Err(anyhow::anyhow!("pixel_size must be greater than zero"));
839    }
840
841    let width_u32 =
842        u32::try_from(width).map_err(|_| anyhow::anyhow!("width exceeds u32::MAX: {}", width))?;
843    let height_u32 = u32::try_from(height)
844        .map_err(|_| anyhow::anyhow!("height exceeds u32::MAX: {}", height))?;
845
846    let pixel_count = width
847        .checked_mul(height)
848        .ok_or_else(|| anyhow::anyhow!("Image dimensions overflow: {}x{}", width, height))?;
849
850    let palette_entry_size = match palette_format {
851        PaletteFormat::Rgb | PaletteFormat::Bgr => 3usize,
852        PaletteFormat::RgbX | PaletteFormat::BgrX | PaletteFormat::RgbA | PaletteFormat::BgrA => {
853            4usize
854        }
855    };
856
857    if palettes.len() < palette_entry_size {
858        return Err(anyhow::anyhow!("Palette data is too small"));
859    }
860    if palettes.len() % palette_entry_size != 0 {
861        return Err(anyhow::anyhow!(
862            "Palette length {} is not a multiple of {}",
863            palettes.len(),
864            palette_entry_size
865        ));
866    }
867    let palette_color_count = palettes.len() / palette_entry_size;
868    if palette_color_count == 0 {
869        return Err(anyhow::anyhow!("Palette does not contain any colors"));
870    }
871
872    let (color_type, output_channels) = match palette_format {
873        PaletteFormat::Rgb | PaletteFormat::RgbX => (ImageColorType::Rgb, 3usize),
874        PaletteFormat::Bgr | PaletteFormat::BgrX => (ImageColorType::Bgr, 3usize),
875        PaletteFormat::RgbA => (ImageColorType::Rgba, 4usize),
876        PaletteFormat::BgrA => (ImageColorType::Bgra, 4usize),
877    };
878
879    let palette_table_len = palette_color_count
880        .checked_mul(output_channels)
881        .ok_or_else(|| anyhow::anyhow!("Palette size overflow"))?;
882    let mut palette_table = Vec::with_capacity(palette_table_len);
883    for idx in 0..palette_color_count {
884        let base = idx * palette_entry_size;
885        match palette_format {
886            PaletteFormat::Rgb => {
887                palette_table.extend_from_slice(&palettes[base..base + 3]);
888            }
889            PaletteFormat::Bgr => {
890                palette_table.extend_from_slice(&palettes[base..base + 3]);
891            }
892            PaletteFormat::RgbX => {
893                palette_table.extend_from_slice(&palettes[base..base + 3]);
894            }
895            PaletteFormat::BgrX => {
896                palette_table.extend_from_slice(&palettes[base..base + 3]);
897            }
898            PaletteFormat::RgbA => {
899                palette_table.extend_from_slice(&palettes[base..base + 4]);
900            }
901            PaletteFormat::BgrA => {
902                palette_table.extend_from_slice(&palettes[base..base + 4]);
903            }
904        }
905    }
906
907    let total_bits_required = pixel_count
908        .checked_mul(pixel_size)
909        .ok_or_else(|| anyhow::anyhow!("Pixel count overflow for pixel_size {}", pixel_size))?;
910    if total_bits_required > pixel_data.len() * 8 {
911        return Err(anyhow::anyhow!(
912            "Pixel data too short: need {} bits, have {} bits",
913            total_bits_required,
914            pixel_data.len() * 8
915        ));
916    }
917
918    let output_len = pixel_count
919        .checked_mul(output_channels)
920        .ok_or_else(|| anyhow::anyhow!("Output image size overflow"))?;
921    let mut output = Vec::with_capacity(output_len);
922
923    let stride = output_channels;
924    if pixel_size == 8 {
925        if pixel_data.len() < pixel_count {
926            return Err(anyhow::anyhow!(
927                "Pixel data too short: expected {} bytes, got {}",
928                pixel_count,
929                pixel_data.len()
930            ));
931        }
932        for &index in pixel_data.iter().take(pixel_count) {
933            let idx = index as usize;
934            if idx >= palette_color_count {
935                return Err(anyhow::anyhow!(
936                    "Palette index {} exceeds palette size {}",
937                    idx,
938                    palette_color_count
939                ));
940            }
941            let start = idx * stride;
942            output.extend_from_slice(&palette_table[start..start + stride]);
943        }
944    } else {
945        let mut bit_offset = 0usize;
946        for _ in 0..pixel_count {
947            let idx = read_bits_as_usize(pixel_data, bit_offset, pixel_size)?;
948            bit_offset = bit_offset
949                .checked_add(pixel_size)
950                .ok_or_else(|| anyhow::anyhow!("Bit offset overflow"))?;
951            if idx >= palette_color_count {
952                return Err(anyhow::anyhow!(
953                    "Palette index {} exceeds palette size {}",
954                    idx,
955                    palette_color_count
956                ));
957            }
958            let start = idx * stride;
959            output.extend_from_slice(&palette_table[start..start + stride]);
960        }
961    }
962
963    Ok(ImageData {
964        width: width_u32,
965        height: height_u32,
966        color_type,
967        depth: 8,
968        data: output,
969    })
970}
971
972fn read_bits_as_usize(data: &[u8], bit_offset: usize, bit_len: usize) -> Result<usize> {
973    if bit_len == 0 {
974        return Err(anyhow::anyhow!("bit_len must be greater than zero"));
975    }
976    if bit_len > (std::mem::size_of::<usize>() * 8) {
977        return Err(anyhow::anyhow!("Cannot read {} bits into usize", bit_len));
978    }
979
980    let mut value = 0usize;
981    for bit_idx in 0..bit_len {
982        let absolute_bit = bit_offset + bit_idx;
983        let byte_index = absolute_bit / 8;
984        if byte_index >= data.len() {
985            return Err(anyhow::anyhow!(
986                "Bit offset {} exceeds pixel data",
987                absolute_bit
988            ));
989        }
990        let bit_in_byte = 7 - (absolute_bit % 8);
991        let bit = (data[byte_index] >> bit_in_byte) & 1;
992        value = (value << 1) | bit as usize;
993    }
994    Ok(value)
995}