1#[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
10pub 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
28pub 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]); new_data.push(chunk[1]); new_data.push(chunk[2]); new_data.push(255); }
47 data.data = new_data;
48 data.color_type = ImageColorType::Bgra;
49 Ok(())
50}
51
52pub 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
73pub 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]); new_data.push(chunk[1]); new_data.push(chunk[2]); }
91 data.data = new_data;
92 data.color_type = ImageColorType::Bgr;
93 Ok(())
94}
95
96pub 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
117pub 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]); new_data.push(chunk[1]); new_data.push(chunk[2]); new_data.push(255); }
136 data.data = new_data;
137 data.color_type = ImageColorType::Rgba;
138 Ok(())
139}
140
141pub 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
162pub 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
183pub 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); new_data.push(gray); new_data.push(gray); }
199 data.data = new_data;
200 data.color_type = ImageColorType::Rgb;
201 Ok(())
202}
203
204pub 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); new_data.push(gray); new_data.push(gray); new_data.push(255); }
221 data.data = new_data;
222 data.color_type = ImageColorType::Rgba;
223 Ok(())
224}
225
226pub 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
237pub 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
253pub 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 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 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
410pub 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, };
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!(); }
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#[cfg(feature = "webp")]
529pub fn load_webp<R: std::io::Read>(mut data: R) -> Result<ImageData> {
530 use std::io::Read;
531 let mut header = [0; 12];
532 data.read_exact(&mut header)?;
533 if !header.starts_with(b"RIFF") || !header.ends_with(b"WEBP") {
534 anyhow::bail!("File is not a webp image.");
535 }
536 let file_size = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
537 let mut da = Vec::with_capacity(file_size as usize + 8);
538 da.extend_from_slice(&header);
539 data.take(file_size as u64 - 4).read_to_end(&mut da)?;
540 let decoder = webp::Decoder::new(&da);
541 let image = decoder
542 .decode()
543 .ok_or(anyhow::anyhow!("Failed to decode WebP image"))?;
544 let color_type = if image.is_alpha() {
545 ImageColorType::Rgba
546 } else {
547 ImageColorType::Rgb
548 };
549 let width = image.width();
550 let height = image.height();
551 let stride = width as usize * color_type.bpp(8) as usize / 8;
552 let mut data = vec![0; stride * height as usize];
553 if image.len() != data.len() {
554 return Err(anyhow::anyhow!(
555 "WebP image data size mismatch: expected {}, got {}",
556 data.len(),
557 image.len()
558 ));
559 }
560 data.copy_from_slice(&image);
561 Ok(ImageData {
562 width,
563 height,
564 depth: 8,
565 color_type,
566 data,
567 })
568}
569
570#[cfg(feature = "qoi")]
571pub fn load_qoi<R: std::io::Read>(data: R) -> Result<ImageData> {
572 let mut decoder = qoi::Decoder::from_stream(data)?;
573 let data = decoder.decode_to_vec()?;
574 let header = decoder.header();
575 Ok(ImageData {
576 width: header.width,
577 height: header.height,
578 color_type: match header.channels {
579 qoi::Channels::Rgb => ImageColorType::Rgb,
580 qoi::Channels::Rgba => ImageColorType::Rgba,
581 },
582 depth: 8,
583 data,
584 })
585}
586
587pub fn decode_img(typ: ImageOutputType, filename: &str) -> Result<ImageData> {
592 match typ {
593 ImageOutputType::Png => {
594 let file = crate::utils::files::read_file(filename)?;
595 let reader = MemReader::new(file);
596 load_png(reader)
597 }
598 #[cfg(feature = "image-jpg")]
599 ImageOutputType::Jpg => {
600 let file = crate::utils::files::read_file(filename)?;
601 load_jpg(&file[..])
602 }
603 #[cfg(feature = "image-webp")]
604 ImageOutputType::Webp => {
605 let file = crate::utils::files::read_file(filename)?;
606 let decoder = webp::Decoder::new(&file);
607 let image = decoder
608 .decode()
609 .ok_or(anyhow::anyhow!("Failed to decode WebP image"))?;
610 let color_type = if image.is_alpha() {
611 ImageColorType::Rgba
612 } else {
613 ImageColorType::Rgb
614 };
615 let width = image.width();
616 let height = image.height();
617 let stride = width as usize * color_type.bpp(8) as usize / 8;
618 let mut data = vec![0; stride * height as usize];
619 if image.len() != data.len() {
620 return Err(anyhow::anyhow!(
621 "WebP image data size mismatch: expected {}, got {}",
622 data.len(),
623 image.len()
624 ));
625 }
626 data.copy_from_slice(&image);
627 Ok(ImageData {
628 width,
629 height,
630 depth: 8,
631 color_type,
632 data,
633 })
634 }
635 #[cfg(feature = "image-jxl")]
636 ImageOutputType::Jxl => {
637 let file = crate::utils::files::read_file(filename)?;
638 decode_jxl(&file[..])
639 }
640 }
641}
642
643pub fn draw_on_canvas(
653 img: ImageData,
654 canvas_width: u32,
655 canvas_height: u32,
656 offset_x: u32,
657 offset_y: u32,
658) -> Result<ImageData> {
659 let bytes_per_pixel = img.color_type.bpp(img.depth) as u32 / 8;
660 let mut canvas_data = vec![0u8; (canvas_width * canvas_height * bytes_per_pixel) as usize];
661 let canvas_stride = canvas_width * bytes_per_pixel;
662 let img_stride = img.width * bytes_per_pixel;
663
664 for y in 0..img.height {
665 let canvas_y = y + offset_y;
666 if canvas_y >= canvas_height {
667 continue;
668 }
669 let canvas_start = (canvas_y * canvas_stride + offset_x * bytes_per_pixel) as usize;
670 let img_start = (y * img_stride) as usize;
671 let copy_len = img_stride as usize;
672 if canvas_start + copy_len > canvas_data.len() {
673 continue;
674 }
675 canvas_data[canvas_start..canvas_start + copy_len]
676 .copy_from_slice(&img.data[img_start..img_start + copy_len]);
677 }
678
679 Ok(ImageData {
680 width: canvas_width,
681 height: canvas_height,
682 color_type: img.color_type,
683 depth: img.depth,
684 data: canvas_data,
685 })
686}
687
688pub fn flip_image(data: &mut ImageData) -> Result<()> {
690 if data.height <= 1 {
691 return Ok(());
692 }
693 let row_size = data.color_type.bpp(data.depth) as usize * data.width as usize / 8;
694 if row_size == 0 {
695 return Ok(());
696 }
697
698 let mut i = 0;
699 let mut j = data.height as usize - 1;
700 while i < j {
701 let (top, bottom) = data.data.split_at_mut(j * row_size);
702 let top_row = &mut top[i * row_size..i * row_size + row_size];
703 let bottom_row = &mut bottom[0..row_size];
704 top_row.swap_with_slice(bottom_row);
705 i += 1;
706 j -= 1;
707 }
708
709 Ok(())
710}
711
712pub fn apply_opacity(img: &mut ImageData, opacity: u8) -> Result<()> {
716 if img.color_type != ImageColorType::Rgba && img.color_type != ImageColorType::Bgra {
717 return Err(anyhow::anyhow!("Image is not RGBA or BGRA"));
718 }
719 if img.depth != 8 {
720 return Err(anyhow::anyhow!(
721 "Opacity application only supports 8-bit depth"
722 ));
723 }
724 for i in (0..img.data.len()).step_by(4) {
725 img.data[i + 3] = (img.data[i + 3] as u16 * opacity as u16 / 255) as u8;
726 }
727 Ok(())
728}
729
730pub fn draw_on_image(base: &mut ImageData, diff: &ImageData, left: u32, top: u32) -> Result<()> {
737 if base.color_type != diff.color_type {
738 return Err(anyhow::anyhow!("Image color types do not match"));
739 }
740 if base.depth != diff.depth {
741 return Err(anyhow::anyhow!("Image depths do not match"));
742 }
743
744 let bits_per_pixel = base.color_type.bpp(base.depth) as usize;
745 if bits_per_pixel == 0 || bits_per_pixel % 8 != 0 {
746 return Err(anyhow::anyhow!(
747 "Unsupported pixel bit layout: {} bits",
748 bits_per_pixel
749 ));
750 }
751 let bpp = bits_per_pixel / 8;
752
753 let base_stride = base.width as usize * bpp;
754 let diff_stride = diff.width as usize * bpp;
755
756 for y in 0..diff.height {
757 let base_y = top + y;
758 if base_y >= base.height {
759 continue;
760 }
761
762 for x in 0..diff.width {
763 let base_x = left + x;
764 if base_x >= base.width {
765 continue;
766 }
767
768 let diff_idx = (y as usize * diff_stride) + (x as usize * bpp);
769 let base_idx = (base_y as usize * base_stride) + (base_x as usize * bpp);
770
771 if diff_idx + bpp > diff.data.len() || base_idx + bpp > base.data.len() {
773 continue;
774 }
775
776 base.data[base_idx..base_idx + bpp]
777 .copy_from_slice(&diff.data[diff_idx..diff_idx + bpp]);
778 }
779 }
780
781 Ok(())
782}
783
784pub fn draw_on_img_with_opacity(
792 base: &mut ImageData,
793 diff: &ImageData,
794 left: u32,
795 top: u32,
796 opacity: u8,
797) -> Result<()> {
798 if base.color_type != diff.color_type {
799 return Err(anyhow::anyhow!("Image color types do not match"));
800 }
801 if base.color_type != ImageColorType::Rgba && base.color_type != ImageColorType::Bgra {
802 return Err(anyhow::anyhow!("Images are not RGBA or BGRA"));
803 }
804 if base.depth != 8 || diff.depth != 8 {
805 return Err(anyhow::anyhow!(
806 "Image drawing with opacity only supports 8-bit depth"
807 ));
808 }
809
810 let bpp = 4;
811 let base_stride = base.width as usize * bpp;
812 let diff_stride = diff.width as usize * bpp;
813
814 for y in 0..diff.height {
815 let base_y = top + y;
816 if base_y >= base.height {
817 continue;
818 }
819
820 for x in 0..diff.width {
821 let base_x = left + x;
822 if base_x >= base.width {
823 continue;
824 }
825
826 let diff_idx = (y as usize * diff_stride) + (x as usize * bpp);
827 let base_idx = (base_y as usize * base_stride) + (base_x as usize * bpp);
828
829 let diff_pixel = &diff.data[diff_idx..diff_idx + bpp];
830 let base_pixel_orig = base.data[base_idx..base_idx + bpp].to_vec();
831
832 let src_alpha_u16 = (diff_pixel[3] as u16 * opacity as u16) / 255;
833
834 if src_alpha_u16 == 0 {
835 continue;
836 }
837
838 let dst_alpha_u16 = base_pixel_orig[3] as u16;
839
840 let out_alpha_u16 = src_alpha_u16 + (dst_alpha_u16 * (255 - src_alpha_u16)) / 255;
842
843 if out_alpha_u16 == 0 {
844 for i in 0..4 {
845 base.data[base_idx + i] = 0;
846 }
847 continue;
848 }
849
850 for i in 0..3 {
852 let src_comp = diff_pixel[i] as u16;
853 let dst_comp = base_pixel_orig[i] as u16;
854
855 let numerator = src_comp as u32 * src_alpha_u16 as u32
856 + (dst_comp as u32 * dst_alpha_u16 as u32 * (255 - src_alpha_u16) as u32) / 255;
857 base.data[base_idx + i] = (numerator / out_alpha_u16 as u32) as u8;
858 }
859 base.data[base_idx + 3] = out_alpha_u16 as u8;
860 }
861 }
862
863 Ok(())
864}
865
866#[derive(Debug, Clone, Copy, PartialEq, Eq)]
867pub enum PaletteFormat {
868 Rgb,
870 Bgr,
872 RgbX,
874 BgrX,
876 RgbA,
878 BgrA,
880}
881
882pub fn convert_index_palette_to_normal_bitmap(
886 pixel_data: &[u8],
887 pixel_size: usize,
888 palettes: &[u8],
889 palette_format: PaletteFormat,
890 width: usize,
891 height: usize,
892) -> Result<ImageData> {
893 if width == 0 || height == 0 {
894 return Err(anyhow::anyhow!("Image dimensions must be non-zero"));
895 }
896 if pixel_size == 0 {
897 return Err(anyhow::anyhow!("pixel_size must be greater than zero"));
898 }
899
900 let width_u32 =
901 u32::try_from(width).map_err(|_| anyhow::anyhow!("width exceeds u32::MAX: {}", width))?;
902 let height_u32 = u32::try_from(height)
903 .map_err(|_| anyhow::anyhow!("height exceeds u32::MAX: {}", height))?;
904
905 let pixel_count = width
906 .checked_mul(height)
907 .ok_or_else(|| anyhow::anyhow!("Image dimensions overflow: {}x{}", width, height))?;
908
909 let palette_entry_size = match palette_format {
910 PaletteFormat::Rgb | PaletteFormat::Bgr => 3usize,
911 PaletteFormat::RgbX | PaletteFormat::BgrX | PaletteFormat::RgbA | PaletteFormat::BgrA => {
912 4usize
913 }
914 };
915
916 if palettes.len() < palette_entry_size {
917 return Err(anyhow::anyhow!("Palette data is too small"));
918 }
919 if palettes.len() % palette_entry_size != 0 {
920 return Err(anyhow::anyhow!(
921 "Palette length {} is not a multiple of {}",
922 palettes.len(),
923 palette_entry_size
924 ));
925 }
926 let palette_color_count = palettes.len() / palette_entry_size;
927 if palette_color_count == 0 {
928 return Err(anyhow::anyhow!("Palette does not contain any colors"));
929 }
930
931 let (color_type, output_channels) = match palette_format {
932 PaletteFormat::Rgb | PaletteFormat::RgbX => (ImageColorType::Rgb, 3usize),
933 PaletteFormat::Bgr | PaletteFormat::BgrX => (ImageColorType::Bgr, 3usize),
934 PaletteFormat::RgbA => (ImageColorType::Rgba, 4usize),
935 PaletteFormat::BgrA => (ImageColorType::Bgra, 4usize),
936 };
937
938 let palette_table_len = palette_color_count
939 .checked_mul(output_channels)
940 .ok_or_else(|| anyhow::anyhow!("Palette size overflow"))?;
941 let mut palette_table = Vec::with_capacity(palette_table_len);
942 for idx in 0..palette_color_count {
943 let base = idx * palette_entry_size;
944 match palette_format {
945 PaletteFormat::Rgb => {
946 palette_table.extend_from_slice(&palettes[base..base + 3]);
947 }
948 PaletteFormat::Bgr => {
949 palette_table.extend_from_slice(&palettes[base..base + 3]);
950 }
951 PaletteFormat::RgbX => {
952 palette_table.extend_from_slice(&palettes[base..base + 3]);
953 }
954 PaletteFormat::BgrX => {
955 palette_table.extend_from_slice(&palettes[base..base + 3]);
956 }
957 PaletteFormat::RgbA => {
958 palette_table.extend_from_slice(&palettes[base..base + 4]);
959 }
960 PaletteFormat::BgrA => {
961 palette_table.extend_from_slice(&palettes[base..base + 4]);
962 }
963 }
964 }
965
966 let total_bits_required = pixel_count
967 .checked_mul(pixel_size)
968 .ok_or_else(|| anyhow::anyhow!("Pixel count overflow for pixel_size {}", pixel_size))?;
969 if total_bits_required > pixel_data.len() * 8 {
970 return Err(anyhow::anyhow!(
971 "Pixel data too short: need {} bits, have {} bits",
972 total_bits_required,
973 pixel_data.len() * 8
974 ));
975 }
976
977 let output_len = pixel_count
978 .checked_mul(output_channels)
979 .ok_or_else(|| anyhow::anyhow!("Output image size overflow"))?;
980 let mut output = Vec::with_capacity(output_len);
981
982 let stride = output_channels;
983 if pixel_size == 8 {
984 if pixel_data.len() < pixel_count {
985 return Err(anyhow::anyhow!(
986 "Pixel data too short: expected {} bytes, got {}",
987 pixel_count,
988 pixel_data.len()
989 ));
990 }
991 for &index in pixel_data.iter().take(pixel_count) {
992 let idx = index as usize;
993 if idx >= palette_color_count {
994 return Err(anyhow::anyhow!(
995 "Palette index {} exceeds palette size {}",
996 idx,
997 palette_color_count
998 ));
999 }
1000 let start = idx * stride;
1001 output.extend_from_slice(&palette_table[start..start + stride]);
1002 }
1003 } else {
1004 let mut bit_offset = 0usize;
1005 for _ in 0..pixel_count {
1006 let idx = read_bits_as_usize(pixel_data, bit_offset, pixel_size)?;
1007 bit_offset = bit_offset
1008 .checked_add(pixel_size)
1009 .ok_or_else(|| anyhow::anyhow!("Bit offset overflow"))?;
1010 if idx >= palette_color_count {
1011 return Err(anyhow::anyhow!(
1012 "Palette index {} exceeds palette size {}",
1013 idx,
1014 palette_color_count
1015 ));
1016 }
1017 let start = idx * stride;
1018 output.extend_from_slice(&palette_table[start..start + stride]);
1019 }
1020 }
1021
1022 Ok(ImageData {
1023 width: width_u32,
1024 height: height_u32,
1025 color_type,
1026 depth: 8,
1027 data: output,
1028 })
1029}
1030
1031fn read_bits_as_usize(data: &[u8], bit_offset: usize, bit_len: usize) -> Result<usize> {
1032 if bit_len == 0 {
1033 return Err(anyhow::anyhow!("bit_len must be greater than zero"));
1034 }
1035 if bit_len > (std::mem::size_of::<usize>() * 8) {
1036 return Err(anyhow::anyhow!("Cannot read {} bits into usize", bit_len));
1037 }
1038
1039 let mut value = 0usize;
1040 for bit_idx in 0..bit_len {
1041 let absolute_bit = bit_offset + bit_idx;
1042 let byte_index = absolute_bit / 8;
1043 if byte_index >= data.len() {
1044 return Err(anyhow::anyhow!(
1045 "Bit offset {} exceeds pixel data",
1046 absolute_bit
1047 ));
1048 }
1049 let bit_in_byte = 7 - (absolute_bit % 8);
1050 let bit = (data[byte_index] >> bit_in_byte) & 1;
1051 value = (value << 1) | bit as usize;
1052 }
1053 Ok(value)
1054}