1use crate::types::*;
3use anyhow::Result;
4
5pub fn reverse_alpha_values(data: &mut ImageData) -> Result<()> {
9 if data.color_type != ImageColorType::Rgba && data.color_type != ImageColorType::Bgra {
10 return Err(anyhow::anyhow!("Image is not RGBA or BGRA"));
11 }
12 if data.depth != 8 {
13 return Err(anyhow::anyhow!(
14 "Alpha value reversal only supports 8-bit depth"
15 ));
16 }
17 for i in (0..data.data.len()).step_by(4) {
18 data.data[i + 3] = 255 - data.data[i + 3];
19 }
20 Ok(())
21}
22
23pub fn convert_bgr_to_bgra(data: &mut ImageData) -> Result<()> {
27 if data.color_type != ImageColorType::Bgr {
28 return Err(anyhow::anyhow!("Image is not BGR"));
29 }
30 if data.depth != 8 {
31 return Err(anyhow::anyhow!(
32 "BGR to BGRA conversion only supports 8-bit depth"
33 ));
34 }
35 let mut new_data = Vec::with_capacity(data.data.len() / 3 * 4);
36 for chunk in data.data.chunks_exact(3) {
37 new_data.push(chunk[0]); new_data.push(chunk[1]); new_data.push(chunk[2]); new_data.push(255); }
42 data.data = new_data;
43 data.color_type = ImageColorType::Bgra;
44 Ok(())
45}
46
47pub fn convert_bgr_to_rgb(data: &mut ImageData) -> Result<()> {
51 if data.color_type != ImageColorType::Bgr {
52 return Err(anyhow::anyhow!("Image is not BGR"));
53 }
54 if data.depth != 8 {
55 return Err(anyhow::anyhow!(
56 "BGR to RGB conversion only supports 8-bit depth"
57 ));
58 }
59 for i in (0..data.data.len()).step_by(3) {
60 let b = data.data[i];
61 data.data[i] = data.data[i + 2];
62 data.data[i + 2] = b;
63 }
64 data.color_type = ImageColorType::Rgb;
65 Ok(())
66}
67
68pub fn convert_bgra_to_bgr(data: &mut ImageData) -> Result<()> {
72 if data.color_type != ImageColorType::Bgra {
73 return Err(anyhow::anyhow!("Image is not BGRA"));
74 }
75 if data.depth != 8 {
76 return Err(anyhow::anyhow!(
77 "BGRA to BGR conversion only supports 8-bit depth"
78 ));
79 }
80 let mut new_data = Vec::with_capacity(data.data.len() / 4 * 3);
81 for chunk in data.data.chunks_exact(4) {
82 new_data.push(chunk[0]); new_data.push(chunk[1]); new_data.push(chunk[2]); }
86 data.data = new_data;
87 data.color_type = ImageColorType::Bgr;
88 Ok(())
89}
90
91pub fn convert_bgra_to_rgba(data: &mut ImageData) -> Result<()> {
95 if data.color_type != ImageColorType::Bgra {
96 return Err(anyhow::anyhow!("Image is not BGRA"));
97 }
98 if data.depth != 8 {
99 return Err(anyhow::anyhow!(
100 "BGRA to RGBA conversion only supports 8-bit depth"
101 ));
102 }
103 for i in (0..data.data.len()).step_by(4) {
104 let b = data.data[i];
105 data.data[i] = data.data[i + 2];
106 data.data[i + 2] = b;
107 }
108 data.color_type = ImageColorType::Rgba;
109 Ok(())
110}
111
112pub fn convert_rgb_to_rgba(data: &mut ImageData) -> Result<()> {
116 if data.color_type != ImageColorType::Rgb {
117 return Err(anyhow::anyhow!("Image is not RGB"));
118 }
119 if data.depth != 8 {
120 return Err(anyhow::anyhow!(
121 "RGB to RGBA conversion only supports 8-bit depth"
122 ));
123 }
124 let mut new_data = Vec::with_capacity(data.data.len() / 3 * 4);
125 for chunk in data.data.chunks_exact(3) {
126 new_data.push(chunk[0]); new_data.push(chunk[1]); new_data.push(chunk[2]); new_data.push(255); }
131 data.data = new_data;
132 data.color_type = ImageColorType::Rgba;
133 Ok(())
134}
135
136pub fn convert_rgb_to_bgr(data: &mut ImageData) -> Result<()> {
140 if data.color_type != ImageColorType::Rgb {
141 return Err(anyhow::anyhow!("Image is not RGB"));
142 }
143 if data.depth != 8 {
144 return Err(anyhow::anyhow!(
145 "RGB to BGR conversion only supports 8-bit depth"
146 ));
147 }
148 for i in (0..data.data.len()).step_by(3) {
149 let r = data.data[i];
150 data.data[i] = data.data[i + 2];
151 data.data[i + 2] = r;
152 }
153 data.color_type = ImageColorType::Bgr;
154 Ok(())
155}
156
157pub fn convert_rgba_to_bgra(data: &mut ImageData) -> Result<()> {
161 if data.color_type != ImageColorType::Rgba {
162 return Err(anyhow::anyhow!("Image is not RGBA"));
163 }
164 if data.depth != 8 {
165 return Err(anyhow::anyhow!(
166 "RGBA to BGRA conversion only supports 8-bit depth"
167 ));
168 }
169 for i in (0..data.data.len()).step_by(4) {
170 let r = data.data[i];
171 data.data[i] = data.data[i + 2];
172 data.data[i + 2] = r;
173 }
174 data.color_type = ImageColorType::Bgra;
175 Ok(())
176}
177
178pub fn encode_img(
185 mut data: ImageData,
186 typ: ImageOutputType,
187 filename: &str,
188 config: &ExtraConfig,
189) -> Result<()> {
190 match typ {
191 ImageOutputType::Png => {
192 let mut file = crate::utils::files::write_file(filename)?;
193 let color_type = match data.color_type {
194 ImageColorType::Grayscale => png::ColorType::Grayscale,
195 ImageColorType::Rgb => png::ColorType::Rgb,
196 ImageColorType::Rgba => png::ColorType::Rgba,
197 ImageColorType::Bgr => {
198 convert_bgr_to_rgb(&mut data)?;
199 png::ColorType::Rgb
200 }
201 ImageColorType::Bgra => {
202 convert_bgra_to_rgba(&mut data)?;
203 png::ColorType::Rgba
204 }
205 };
206 let bit_depth = match &data.depth {
207 1 => png::BitDepth::One,
208 2 => png::BitDepth::Two,
209 4 => png::BitDepth::Four,
210 8 => png::BitDepth::Eight,
211 16 => png::BitDepth::Sixteen,
212 _ => return Err(anyhow::anyhow!("Unsupported bit depth: {}", data.depth)),
213 };
214 let mut encoder = png::Encoder::new(&mut file, data.width, data.height);
215 encoder.set_color(color_type);
216 encoder.set_depth(bit_depth);
217 encoder.set_compression(config.png_compression_level.to_compression());
218 let mut writer = encoder.write_header()?;
219 writer.write_image_data(&data.data)?;
220 writer.finish()?;
221 Ok(())
222 }
223 #[cfg(feature = "image-jpg")]
224 ImageOutputType::Jpg => {
225 let file = crate::utils::files::write_file(filename)?;
226 let color_type = match data.color_type {
227 ImageColorType::Grayscale => mozjpeg::ColorSpace::JCS_GRAYSCALE,
228 ImageColorType::Rgb => mozjpeg::ColorSpace::JCS_RGB,
229 ImageColorType::Rgba => mozjpeg::ColorSpace::JCS_EXT_RGBA,
230 ImageColorType::Bgr => {
231 convert_bgr_to_rgb(&mut data)?;
232 mozjpeg::ColorSpace::JCS_RGB
233 }
234 ImageColorType::Bgra => {
235 convert_bgra_to_rgba(&mut data)?;
236 mozjpeg::ColorSpace::JCS_EXT_RGBA
237 }
238 };
239 if data.depth != 8 {
240 return Err(anyhow::anyhow!(
241 "JPEG encoding only supports 8-bit depth, found: {}",
242 data.depth
243 ));
244 }
245 let mut encoder = mozjpeg::compress::Compress::new(color_type);
246 encoder.set_size(data.width as usize, data.height as usize);
247 encoder.set_quality(config.jpeg_quality as f32);
248 let mut start = encoder.start_compress(file)?;
249 start.write_scanlines(&data.data)?;
250 start.finish()?;
251 Ok(())
252 }
253 #[cfg(feature = "image-webp")]
254 ImageOutputType::Webp => {
255 let mut file = crate::utils::files::write_file(filename)?;
256 let color_type = match data.color_type {
257 ImageColorType::Rgb => webp::PixelLayout::Rgb,
258 ImageColorType::Rgba => webp::PixelLayout::Rgba,
259 ImageColorType::Bgr => {
260 convert_bgr_to_rgb(&mut data)?;
261 webp::PixelLayout::Rgb
262 }
263 ImageColorType::Bgra => {
264 convert_bgra_to_rgba(&mut data)?;
265 webp::PixelLayout::Rgba
266 }
267 _ => {
268 return Err(anyhow::anyhow!(
269 "Unsupported color type for WebP: {:?}",
270 data.color_type
271 ));
272 }
273 };
274 if data.depth != 8 {
275 return Err(anyhow::anyhow!(
276 "WebP encoding only supports 8-bit depth, found: {}",
277 data.depth
278 ));
279 }
280 let encoder = webp::Encoder::new(&data.data, color_type, data.width, data.height);
281 let re = encoder
282 .encode_simple(config.webp_lossless, config.webp_quality as f32)
283 .map_err(|e| anyhow::anyhow!("Failed to encode WebP image: {:?}", e))?;
284 file.write_all(&re)?;
285 Ok(())
286 }
287 }
288}
289
290pub fn load_png<R: std::io::Read>(data: R) -> Result<ImageData> {
292 let decoder = png::Decoder::new(data);
293 let mut reader = decoder.read_info()?;
294 let bit_depth = match reader.info().bit_depth {
295 png::BitDepth::One => 1,
296 png::BitDepth::Two => 2,
297 png::BitDepth::Four => 4,
298 png::BitDepth::Eight => 8,
299 png::BitDepth::Sixteen => 16,
300 };
301 let color_type = match reader.info().color_type {
302 png::ColorType::Grayscale => ImageColorType::Grayscale,
303 png::ColorType::Rgb => ImageColorType::Rgb,
304 png::ColorType::Rgba => ImageColorType::Rgba,
305 _ => {
306 return Err(anyhow::anyhow!(
307 "Unsupported color type: {:?}",
308 reader.info().color_type
309 ));
310 }
311 };
312 let stride = reader.info().width as usize * color_type.bpp(bit_depth) as usize / 8;
313 let mut data = vec![0; stride * reader.info().height as usize];
314 reader.next_frame(&mut data)?;
315 Ok(ImageData {
316 width: reader.info().width,
317 height: reader.info().height,
318 depth: bit_depth,
319 color_type,
320 data,
321 })
322}
323
324#[cfg(feature = "mozjpeg")]
325pub fn load_jpg<R: std::io::Read>(data: R) -> Result<ImageData> {
326 let decoder = mozjpeg::decompress::Decompress::new_reader(std::io::BufReader::new(data))?;
327 let color_type = match decoder.color_space() {
328 mozjpeg::ColorSpace::JCS_GRAYSCALE => ImageColorType::Grayscale,
329 mozjpeg::ColorSpace::JCS_RGB => ImageColorType::Rgb,
330 mozjpeg::ColorSpace::JCS_EXT_RGBA => ImageColorType::Rgba,
331 _ => ImageColorType::Rgb, };
333 let width = decoder.width() as u32;
334 let height = decoder.height() as u32;
335 let stride = width as usize * color_type.bpp(8) as usize / 8;
336 let mut data = vec![0; stride * height as usize];
337 let mut re = match color_type {
338 ImageColorType::Grayscale => decoder.grayscale()?,
339 ImageColorType::Rgb => decoder.rgb()?,
340 ImageColorType::Rgba => decoder.rgba()?,
341 _ => {
342 unreachable!(); }
344 };
345 re.read_scanlines_into(&mut data)?;
346 Ok(ImageData {
347 width,
348 height,
349 depth: 8,
350 color_type,
351 data,
352 })
353}
354
355pub fn decode_img(typ: ImageOutputType, filename: &str) -> Result<ImageData> {
360 match typ {
361 ImageOutputType::Png => {
362 let file = crate::utils::files::read_file(filename)?;
363 load_png(&file[..])
364 }
365 #[cfg(feature = "image-jpg")]
366 ImageOutputType::Jpg => {
367 let file = crate::utils::files::read_file(filename)?;
368 load_jpg(&file[..])
369 }
370 #[cfg(feature = "image-webp")]
371 ImageOutputType::Webp => {
372 let file = crate::utils::files::read_file(filename)?;
373 let decoder = webp::Decoder::new(&file);
374 let image = decoder
375 .decode()
376 .ok_or(anyhow::anyhow!("Failed to decode WebP image"))?;
377 let color_type = if image.is_alpha() {
378 ImageColorType::Rgba
379 } else {
380 ImageColorType::Rgb
381 };
382 let width = image.width();
383 let height = image.height();
384 let stride = width as usize * color_type.bpp(8) as usize / 8;
385 let mut data = vec![0; stride * height as usize];
386 if image.len() != data.len() {
387 return Err(anyhow::anyhow!(
388 "WebP image data size mismatch: expected {}, got {}",
389 data.len(),
390 image.len()
391 ));
392 }
393 data.copy_from_slice(&image);
394 Ok(ImageData {
395 width,
396 height,
397 depth: 8,
398 color_type,
399 data,
400 })
401 }
402 }
403}
404
405pub fn draw_on_canvas(
415 img: ImageData,
416 canvas_width: u32,
417 canvas_height: u32,
418 offset_x: u32,
419 offset_y: u32,
420) -> Result<ImageData> {
421 let bytes_per_pixel = img.color_type.bpp(img.depth) as u32 / 8;
422 let mut canvas_data = vec![0u8; (canvas_width * canvas_height * bytes_per_pixel) as usize];
423 let canvas_stride = canvas_width * bytes_per_pixel;
424 let img_stride = img.width * bytes_per_pixel;
425
426 for y in 0..img.height {
427 let canvas_y = y + offset_y;
428 if canvas_y >= canvas_height {
429 continue;
430 }
431 let canvas_start = (canvas_y * canvas_stride + offset_x * bytes_per_pixel) as usize;
432 let img_start = (y * img_stride) as usize;
433 let copy_len = img_stride as usize;
434 if canvas_start + copy_len > canvas_data.len() {
435 continue;
436 }
437 canvas_data[canvas_start..canvas_start + copy_len]
438 .copy_from_slice(&img.data[img_start..img_start + copy_len]);
439 }
440
441 Ok(ImageData {
442 width: canvas_width,
443 height: canvas_height,
444 color_type: img.color_type,
445 depth: img.depth,
446 data: canvas_data,
447 })
448}
449
450pub fn flip_image(data: &mut ImageData) -> Result<()> {
452 if data.height <= 1 {
453 return Ok(());
454 }
455 let row_size = data.color_type.bpp(data.depth) as usize * data.width as usize / 8;
456 if row_size == 0 {
457 return Ok(());
458 }
459
460 let mut i = 0;
461 let mut j = data.height as usize - 1;
462 while i < j {
463 let (top, bottom) = data.data.split_at_mut(j * row_size);
464 let top_row = &mut top[i * row_size..i * row_size + row_size];
465 let bottom_row = &mut bottom[0..row_size];
466 top_row.swap_with_slice(bottom_row);
467 i += 1;
468 j -= 1;
469 }
470
471 Ok(())
472}
473
474pub fn apply_opacity(img: &mut ImageData, opacity: u8) -> Result<()> {
478 if img.color_type != ImageColorType::Rgba && img.color_type != ImageColorType::Bgra {
479 return Err(anyhow::anyhow!("Image is not RGBA or BGRA"));
480 }
481 if img.depth != 8 {
482 return Err(anyhow::anyhow!(
483 "Opacity application only supports 8-bit depth"
484 ));
485 }
486 for i in (0..img.data.len()).step_by(4) {
487 img.data[i + 3] = (img.data[i + 3] as u16 * opacity as u16 / 255) as u8;
488 }
489 Ok(())
490}
491
492pub fn draw_on_img_with_opacity(
500 base: &mut ImageData,
501 diff: &ImageData,
502 left: u32,
503 top: u32,
504 opacity: u8,
505) -> Result<()> {
506 if base.color_type != diff.color_type {
507 return Err(anyhow::anyhow!("Image color types do not match"));
508 }
509 if base.color_type != ImageColorType::Rgba && base.color_type != ImageColorType::Bgra {
510 return Err(anyhow::anyhow!("Images are not RGBA or BGRA"));
511 }
512 if base.depth != 8 || diff.depth != 8 {
513 return Err(anyhow::anyhow!(
514 "Image drawing with opacity only supports 8-bit depth"
515 ));
516 }
517
518 let bpp = 4;
519 let base_stride = base.width as usize * bpp;
520 let diff_stride = diff.width as usize * bpp;
521
522 for y in 0..diff.height {
523 let base_y = top + y;
524 if base_y >= base.height {
525 continue;
526 }
527
528 for x in 0..diff.width {
529 let base_x = left + x;
530 if base_x >= base.width {
531 continue;
532 }
533
534 let diff_idx = (y as usize * diff_stride) + (x as usize * bpp);
535 let base_idx = (base_y as usize * base_stride) + (base_x as usize * bpp);
536
537 let diff_pixel = &diff.data[diff_idx..diff_idx + bpp];
538 let base_pixel_orig = base.data[base_idx..base_idx + bpp].to_vec();
539
540 let src_alpha_u16 = (diff_pixel[3] as u16 * opacity as u16) / 255;
541
542 if src_alpha_u16 == 0 {
543 continue;
544 }
545
546 let dst_alpha_u16 = base_pixel_orig[3] as u16;
547
548 let out_alpha_u16 = src_alpha_u16 + (dst_alpha_u16 * (255 - src_alpha_u16)) / 255;
550
551 if out_alpha_u16 == 0 {
552 for i in 0..4 {
553 base.data[base_idx + i] = 0;
554 }
555 continue;
556 }
557
558 for i in 0..3 {
560 let src_comp = diff_pixel[i] as u16;
561 let dst_comp = base_pixel_orig[i] as u16;
562
563 let numerator = src_comp * src_alpha_u16
564 + (dst_comp * dst_alpha_u16 * (255 - src_alpha_u16)) / 255;
565 base.data[base_idx + i] = (numerator / out_alpha_u16) as u8;
566 }
567 base.data[base_idx + 3] = out_alpha_u16 as u8;
568 }
569 }
570
571 Ok(())
572}