1#![cfg_attr(not(test), allow(dead_code))]
10#![allow(
11 clippy::many_single_char_names,
12 clippy::similar_names,
13 clippy::cast_possible_truncation,
14 clippy::cast_sign_loss,
15 clippy::doc_markdown,
16 clippy::cast_precision_loss
17)]
18
19use self::helpers::interlace_fields;
20use crate::profile::{Encoding, Profile};
21
22mod cl;
27mod clcl;
28mod helpers;
29mod reordered;
30mod rgb555;
31mod rgb565;
32mod uyvy;
33mod ycbcr420;
34
35pub use cl::encode_cl;
40pub use clcl::encode_clcl;
41pub use reordered::encode_reordered_rgb555;
42pub use rgb555::encode_rgb555;
43pub use rgb565::encode_rgb565;
44pub use uyvy::encode_uyvy;
45pub use ycbcr420::encode_ycbcr420;
46
47#[must_use]
57pub(crate) fn rotate_bgra(src: &[u8], w: i32, h: i32, rotation: i32) -> Vec<u8> {
58 let wu = w as usize;
59 let hu = h as usize;
60 match rotation % 360 {
61 90 => {
62 let mut dst = vec![0u8; wu * hu * 4];
64 for sy in 0..hu {
65 for sx in 0..wu {
66 let s_idx = (sy * wu + sx) * 4;
67 let dx = hu - 1 - sy;
68 let dy = sx;
69 let d_idx = (dy * hu + dx) * 4;
70 dst[d_idx..d_idx + 4].copy_from_slice(&src[s_idx..s_idx + 4]);
71 }
72 }
73 dst
74 }
75 180 => {
76 let mut dst = vec![0u8; wu * hu * 4];
77 for sy in 0..hu {
78 for sx in 0..wu {
79 let s_idx = (sy * wu + sx) * 4;
80 let dx = wu - 1 - sx;
81 let dy = hu - 1 - sy;
82 let d_idx = (dy * wu + dx) * 4;
83 dst[d_idx..d_idx + 4].copy_from_slice(&src[s_idx..s_idx + 4]);
84 }
85 }
86 dst
87 }
88 270 => {
89 let mut dst = vec![0u8; wu * hu * 4];
91 for sy in 0..hu {
92 for sx in 0..wu {
93 let s_idx = (sy * wu + sx) * 4;
94 let dx = sy;
95 let dy = wu - 1 - sx;
96 let d_idx = (dy * hu + dx) * 4;
97 dst[d_idx..d_idx + 4].copy_from_slice(&src[s_idx..s_idx + 4]);
98 }
99 }
100 dst
101 }
102 _ => src.to_vec(),
103 }
104}
105
106#[must_use]
118pub fn build_ithmb_file(bgra: &[u8], w: i32, h: i32, profile: &Profile) -> Vec<u8> {
119 let rotated = if profile.rotation != 0 {
121 rotate_bgra(bgra, w, h, profile.rotation)
122 } else {
123 bgra.to_vec()
124 };
125
126 let encoded = encode_bgra(&rotated, w, h, profile);
128
129 let prefix_bytes = (profile.prefix as u32).to_be_bytes();
131 let mut file = Vec::with_capacity(4 + encoded.len());
132 file.extend_from_slice(&prefix_bytes);
133 file.extend_from_slice(&encoded);
134
135 if profile.is_padded {
137 let total_min = 4 + profile.frame_byte_length as usize;
138 if file.len() < total_min {
139 file.resize(total_min, 0);
140 }
141 }
142
143 file
144}
145
146#[must_use]
161pub fn encode_bgra(src: &[u8], w: i32, h: i32, profile: &Profile) -> Vec<u8> {
162 let encoded: Vec<u8> = if profile.clcl_chroma {
164 encode_clcl(src, w, h)
166 } else if profile.cl_chroma {
167 encode_cl(src, w, h)
169 } else {
170 match profile.encoding {
171 Encoding::Rgb565 => encode_rgb565(src, w, h, !profile.little_endian),
172 Encoding::Rgb555 => encode_rgb555(src, w, h, !profile.little_endian, profile.swap_rgb_channels),
173 Encoding::ReorderedRgb555 => encode_reordered_rgb555(src, w, h, true), Encoding::Yuv422 => encode_uyvy(src, w, h),
175 Encoding::Ycbcr420 => encode_ycbcr420(src, w, h, profile.swap_chroma_planes),
176 Encoding::Jpeg => {
177 Vec::new()
180 }
181 }
182 };
183
184 let interlaced = if profile.is_interlaced {
186 interlace_fields(&encoded, w, h, profile.encoding)
187 } else {
188 encoded
189 };
190
191 let target = profile.frame_byte_length as usize;
193 if interlaced.len() < target {
194 let mut padded = interlaced;
195 padded.resize(target, 0);
196 padded
197 } else {
198 interlaced
199 }
200}
201
202#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::profile::Encoding;
210
211 fn make_rgb_profile(w: i32, h: i32, enc: Encoding, le: bool, swap: bool, interlace: bool) -> Profile {
214 let bpp: i32 = match enc {
215 Encoding::Rgb565 | Encoding::Rgb555 | Encoding::ReorderedRgb555 | Encoding::Yuv422 => 2,
216 Encoding::Ycbcr420 => 3, Encoding::Jpeg => 0,
218 };
219 Profile {
220 prefix: 0,
221 width: w,
222 height: h,
223 encoding: enc,
224 frame_byte_length: w * h * bpp,
225 little_endian: le,
226 swap_rgb_channels: swap,
227 is_interlaced: interlace,
228 ..Default::default()
229 }
230 }
231
232 #[test]
235 fn rgb565_white_1x1_le() {
236 let bgra = vec![255, 255, 255, 255];
238 let enc = encode_rgb565(&bgra, 1, 1, false);
239 assert_eq!(enc, &[0xFF, 0xFF]);
241 }
242
243 #[test]
244 fn rgb565_white_1x1_be() {
245 let bgra = vec![255, 255, 255, 255];
246 let enc = encode_rgb565(&bgra, 1, 1, true);
247 assert_eq!(enc, &[0xFF, 0xFF]);
249 }
250
251 #[test]
252 fn rgb565_black_1x1() {
253 let bgra = vec![0, 0, 0, 255];
254 let enc = encode_rgb565(&bgra, 1, 1, false);
255 assert_eq!(enc, &[0x00, 0x00]);
256 }
257
258 #[test]
259 fn rgb565_red_1x1() {
260 let bgra = vec![0, 0, 255, 255];
264 let enc = encode_rgb565(&bgra, 1, 1, false);
265 assert_eq!(enc, &[0x00, 0xF8]);
266 }
267
268 #[test]
269 fn rgb565_red_1x1_be() {
270 let bgra = vec![0, 0, 255, 255];
271 let enc = encode_rgb565(&bgra, 1, 1, true);
272 assert_eq!(enc, &[0xF8, 0x00]);
274 }
275
276 #[test]
277 fn rgb565_green_1x1() {
278 let bgra = vec![0, 255, 0, 255];
280 let enc = encode_rgb565(&bgra, 1, 1, false);
281 assert_eq!(enc, &[0xE0, 0x07]);
283 }
284
285 #[test]
286 fn rgb565_blue_1x1() {
287 let bgra = vec![255, 0, 0, 255];
289 let enc = encode_rgb565(&bgra, 1, 1, false);
290 assert_eq!(enc, &[0x1F, 0x00]);
292 }
293
294 #[test]
295 fn rgb565_2x1() {
296 let bgra = vec![
298 0, 0, 255, 255, 255, 0, 0, 255, ];
301 let enc = encode_rgb565(&bgra, 2, 1, false);
302 assert_eq!(enc, &[0x00, 0xF8, 0x1F, 0x00]);
304 }
305
306 #[test]
307 fn rgb565_2x2() {
308 let bgra = [0u8, 0, 255, 255].repeat(4);
310 let enc = encode_rgb565(&bgra, 2, 2, false);
311 assert_eq!(enc.len(), 2 * 2 * 2);
312 assert!(enc.iter().all(|&b| b == 0x00 || b == 0xF8));
313 for i in 0..4 {
315 assert_eq!(enc[i * 2], 0x00, "pixel {i} byte 0");
316 assert_eq!(enc[i * 2 + 1], 0xF8, "pixel {i} byte 1");
317 }
318 }
319
320 #[test]
321 fn rgb565_roundtrip() {
322 let bgra = vec![10, 20, 30, 255, 200, 180, 100, 255, 50, 60, 70, 255, 0, 0, 0, 255];
324 let enc = encode_rgb565(&bgra, 2, 2, true);
325 let dec = crate::rgb565::decode(
326 &enc,
327 &make_rgb_profile(2, 2, Encoding::Rgb565, false, false, false),
328 &std::sync::atomic::AtomicBool::new(false),
329 )
330 .unwrap();
331 assert_eq!(dec.data.len(), bgra.len());
332 for i in 0..4 {
334 assert!(
335 (i32::from(dec.data[i * 4]) - i32::from(bgra[i * 4])).abs() <= 8, "B channel mismatch at pixel {i}"
337 );
338 assert!(
339 (i32::from(dec.data[i * 4 + 1]) - i32::from(bgra[i * 4 + 1])).abs() <= 4,
340 "G channel mismatch at pixel {i}"
341 );
342 assert!(
343 (i32::from(dec.data[i * 4 + 2]) - i32::from(bgra[i * 4 + 2])).abs() <= 8,
344 "R channel mismatch at pixel {i}"
345 );
346 assert_eq!(dec.data[i * 4 + 3], 255, "alpha mismatch at pixel {i}");
347 }
348 }
349
350 #[test]
353 fn rgb555_white_1x1() {
354 let bgra = vec![255, 255, 255, 255];
355 let enc = encode_rgb555(&bgra, 1, 1, true, false);
356 assert_eq!(enc, &[0x7F, 0xFF]);
359 }
360
361 #[test]
362 fn rgb555_white_1x1_le() {
363 let bgra = vec![255, 255, 255, 255];
364 let enc = encode_rgb555(&bgra, 1, 1, false, false);
365 assert_eq!(enc, &[0xFF, 0x7F]);
367 }
368
369 #[test]
370 fn rgb555_black_1x1() {
371 let bgra = vec![0, 0, 0, 255];
372 let enc = encode_rgb555(&bgra, 1, 1, true, false);
373 assert_eq!(enc, &[0x00, 0x00]);
374 }
375
376 #[test]
377 fn rgb555_red_1x1() {
378 let bgra = vec![0, 0, 255, 255];
379 let enc = encode_rgb555(&bgra, 1, 1, true, false);
380 assert_eq!(enc, &[0x7C, 0x00]);
382 }
383
384 #[test]
385 fn rgb555_blue_1x1() {
386 let bgra = vec![255, 0, 0, 255];
387 let enc = encode_rgb555(&bgra, 1, 1, true, false);
388 assert_eq!(enc, &[0x00, 0x1F]);
390 }
391
392 #[test]
393 fn rgb555_green_1x1() {
394 let bgra = vec![0, 255, 0, 255];
395 let enc = encode_rgb555(&bgra, 1, 1, true, false);
396 assert_eq!(enc, &[0x03, 0xE0]);
398 }
399
400 #[test]
401 fn rgb555_swap_rgb_bgr15() {
402 let bgra = vec![255, 0, 0, 255];
405 let enc = encode_rgb555(&bgra, 1, 1, true, true);
406 assert_eq!(enc, &[0x7C, 0x00]);
408 }
409
410 #[test]
411 fn rgb555_roundtrip() {
412 let bgra = vec![10, 20, 30, 255, 200, 180, 100, 255];
413 let enc = encode_rgb555(&bgra, 2, 1, true, false);
414 let dec = crate::rgb555::decode(
415 &enc,
416 &make_rgb_profile(2, 1, Encoding::Rgb555, false, false, false),
417 &std::sync::atomic::AtomicBool::new(false),
418 )
419 .unwrap();
420 assert_eq!(dec.data.len(), bgra.len());
421 for i in 0..2 {
422 for c in 0..3 {
423 let diff = (i32::from(dec.data[i * 4 + c]) - i32::from(bgra[i * 4 + c])).abs();
424 assert!(diff <= 8, "channel {c} pixel {i} diff {diff}");
425 }
426 assert_eq!(dec.data[i * 4 + 3], 255);
427 }
428 }
429
430 #[test]
433 fn reordered_rgb555_white_1x1() {
434 let bgra = vec![255, 255, 255, 255];
435 let enc = encode_reordered_rgb555(&bgra, 1, 1, true);
436 assert_eq!(enc, &[0x7F, 0xFF]);
438 }
439
440 #[test]
441 fn reordered_rgb555_red_1x1() {
442 let bgra = vec![0, 0, 255, 255];
443 let enc = encode_reordered_rgb555(&bgra, 1, 1, true);
444 assert_eq!(enc, &[0x7C, 0x00]);
445 }
446
447 #[test]
450 fn uyvy_white_2x1() {
451 let bgra = vec![255u8; 2 * 4];
454 let enc = encode_uyvy(&bgra, 2, 1);
455 assert_eq!(enc, &[128, 255, 128, 255]);
456 }
457
458 #[test]
459 fn uyvy_black_2x1() {
460 let bgra = [0u8, 0, 0, 255].repeat(2);
461 let enc = encode_uyvy(&bgra, 2, 1);
462 assert_eq!(enc, &[128, 0, 128, 0]);
464 }
465
466 #[test]
467 fn uyvy_red_blue_2x1() {
468 let bgra = vec![
470 0, 0, 255, 255, 255, 0, 0, 255, ];
473 let enc = encode_uyvy(&bgra, 2, 1);
474 assert_eq!(enc.len(), 4);
479 assert_eq!(enc[1], 76); assert_eq!(enc[3], 28); assert_eq!(enc[0], 170); assert_eq!(enc[2], 181); }
484
485 #[test]
486 fn uyvy_roundtrip_2x2() {
487 let bgra = vec![128u8; 2 * 2 * 4];
489 let enc = encode_uyvy(&bgra, 2, 2);
490 assert_eq!(enc.len(), 2 * 2 * 2);
491 for block in 0..2 {
493 let off = block * 4;
494 assert_eq!(enc[off + 1], 128); assert_eq!(enc[off + 3], 128); }
497 }
498
499 #[test]
502 fn ycbcr420_white_2x2() {
503 let bgra = vec![255u8; 4 * 4];
505 let enc = encode_ycbcr420(&bgra, 2, 2, false);
506 assert_eq!(enc.len(), 4 + 1 + 1); assert_eq!(&enc[0..4], &[255, 255, 255, 255]);
509 assert_eq!(enc[4], 128); assert_eq!(enc[5], 128); }
512
513 #[test]
514 fn ycbcr420_black_2x2() {
515 let bgra = [0u8, 0, 0, 255].repeat(4);
516 let enc = encode_ycbcr420(&bgra, 2, 2, false);
517 assert_eq!(enc.len(), 6);
518 assert_eq!(&enc[0..4], &[0, 0, 0, 0]);
519 assert_eq!(enc[4], 128);
520 assert_eq!(enc[5], 128);
521 }
522
523 #[test]
524 fn ycbcr420_swap_chroma() {
525 let bgra = vec![255u8; 4 * 4];
526 let enc = encode_ycbcr420(&bgra, 2, 2, true);
527 assert_eq!(enc.len(), 6);
529 assert_eq!(&enc[0..4], &[255, 255, 255, 255]);
530 assert_eq!(enc[4], 128); assert_eq!(enc[5], 128); }
533
534 #[test]
535 fn ycbcr420_2x2_red_green_blue_gray() {
536 let bgra = vec![0, 0, 255, 255, 0, 255, 0, 255, 255, 0, 0, 255, 128, 128, 128, 255];
539 let enc = encode_ycbcr420(&bgra, 2, 2, false);
540 assert_eq!(enc.len(), 6);
541 assert_eq!(enc[0], 76);
544 assert_eq!(enc[1], 149);
545 assert_eq!(enc[2], 28);
546 assert_eq!(enc[3], 128);
547 assert!(enc[4] > 0); assert!(enc[5] > 0); }
551
552 #[test]
555 fn clcl_white_2x1() {
556 let bgra = vec![255u8; 2 * 4];
558 let enc = encode_clcl(&bgra, 2, 1);
559 assert_eq!(enc.len(), 4);
561 assert_eq!(enc[0], 255); assert_eq!(enc[1], 255); assert_eq!(enc[2], 0x88);
567 assert_eq!(enc[3], 0x88);
568 }
569
570 #[test]
571 fn clcl_black_2x1() {
572 let bgra = [0u8, 0, 0, 255].repeat(2);
573 let enc = encode_clcl(&bgra, 2, 1);
574 assert_eq!(enc.len(), 4);
575 assert_eq!(enc[0], 0);
576 assert_eq!(enc[1], 0);
577 assert_eq!(enc[2], 0x88);
578 assert_eq!(enc[3], 0x88);
579 }
580
581 #[test]
582 fn clcl_2x2() {
583 let bgra = [128u8, 128, 128, 255].repeat(4);
584 let enc = encode_clcl(&bgra, 2, 2);
585 assert_eq!(enc.len(), 8);
587 assert_eq!(enc[0], 128);
589 assert_eq!(enc[1], 128);
590 assert_eq!(enc[2], 128);
591 assert_eq!(enc[3], 128);
592 assert_eq!(enc[4], 0x88);
596 assert_eq!(enc[5], 0x88);
597 assert_eq!(enc[6], 0x88);
598 assert_eq!(enc[7], 0x88);
599 }
600
601 #[test]
604 fn cl_white_1x1() {
605 let bgra = vec![255, 255, 255, 255];
606 let enc = encode_cl(&bgra, 1, 1);
607 assert_eq!(enc.len(), 2);
609 assert_eq!(enc[0], 255);
610 assert_eq!(enc[1], 0x88);
612 }
613
614 #[test]
615 fn cl_black_1x1() {
616 let bgra = vec![0, 0, 0, 255];
617 let enc = encode_cl(&bgra, 1, 1);
618 assert_eq!(enc.len(), 2);
619 assert_eq!(enc[0], 0);
620 assert_eq!(enc[1], 0x88);
621 }
622
623 #[test]
624 fn cl_2x1() {
625 let bgra = vec![255u8; 2 * 4];
626 let enc = encode_cl(&bgra, 2, 1);
627 assert_eq!(enc.len(), 4);
629 assert_eq!(enc[0], 255);
630 assert_eq!(enc[1], 255);
631 assert_eq!(enc[2], 0x88);
632 assert_eq!(enc[3], 0x88);
633 }
634
635 #[test]
638 fn clamp_test() {
639 assert_eq!(crate::pixel_utils::clamp_u8(0), 0);
640 assert_eq!(crate::pixel_utils::clamp_u8(255), 255);
641 assert_eq!(crate::pixel_utils::clamp_u8(-10), 0);
642 assert_eq!(crate::pixel_utils::clamp_u8(300), 255);
643 assert_eq!(crate::pixel_utils::clamp_u8(128), 128);
644 }
645
646 #[test]
649 fn rotate_180_2x2() {
650 let bgra = vec![0, 0, 255, 255, 0, 255, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255];
652 let rotated = rotate_bgra(&bgra, 2, 2, 180);
653 assert_eq!(&rotated[0..4], &[255, 255, 255, 255]); assert_eq!(&rotated[4..8], &[255, 0, 0, 255]); assert_eq!(&rotated[8..12], &[0, 255, 0, 255]); assert_eq!(&rotated[12..16], &[0, 0, 255, 255]); }
659
660 #[test]
661 fn rotate_90_2x1() {
662 let bgra = vec![
664 0, 0, 255, 255, 255, 0, 0, 255, ];
667 let rotated = rotate_bgra(&bgra, 2, 1, 90);
668 assert_eq!(rotated.len(), 8); assert_eq!(rotated.len(), 8);
678 }
679
680 #[test]
683 fn encode_bgra_rgb565_le() {
684 let profile = make_rgb_profile(1, 1, Encoding::Rgb565, true, false, false);
685 let bgra = vec![255, 255, 255, 255];
686 let enc = encode_bgra(&bgra, 1, 1, &profile);
687 assert_eq!(enc, &[0xFF, 0xFF]);
688 }
689
690 #[test]
691 fn encode_bgra_clcl() {
692 let mut profile = make_rgb_profile(2, 1, Encoding::Rgb565, true, false, false);
693 profile.clcl_chroma = true;
694 profile.frame_byte_length = 4;
695 let bgra = vec![255u8; 2 * 4];
696 let enc = encode_bgra(&bgra, 2, 1, &profile);
697 assert_eq!(enc.len(), 4);
698 }
699
700 #[test]
701 fn encode_bgra_cl() {
702 let mut profile = make_rgb_profile(1, 1, Encoding::Rgb565, true, false, false);
703 profile.cl_chroma = true;
704 profile.frame_byte_length = 2;
705 let bgra = vec![255, 255, 255, 255];
706 let enc = encode_bgra(&bgra, 1, 1, &profile);
707 assert_eq!(enc.len(), 2);
708 }
709
710 #[test]
711 fn encode_bgra_ycbcr420() {
712 let bgra = vec![255u8; 4 * 4];
715 let enc = encode_ycbcr420(&bgra, 2, 2, false);
716 assert_eq!(enc.len(), 6);
717 }
718
719 #[test]
720 fn encode_bgra_padding() {
721 let mut profile = make_rgb_profile(1, 1, Encoding::Rgb565, true, false, false);
722 profile.frame_byte_length = 10; let bgra = vec![255, 255, 255, 255];
724 let enc = encode_bgra(&bgra, 1, 1, &profile);
725 assert_eq!(enc.len(), 10);
726 assert_eq!(&enc[0..2], &[0xFF, 0xFF]);
727 assert_eq!(&enc[2..], &[0, 0, 0, 0, 0, 0, 0, 0]);
728 }
729
730 #[test]
731 fn encode_bgra_interlace() {
732 let mut profile = make_rgb_profile(2, 2, Encoding::Rgb565, true, false, true);
733 profile.frame_byte_length = 8;
734 let bgra = vec![0, 0, 255, 255, 0, 0, 128, 255, 0, 0, 255, 255, 0, 0, 128, 255];
735 let enc = encode_bgra(&bgra, 2, 2, &profile);
736 assert_eq!(enc.len(), 8);
737 assert_ne!(enc, &[0u8; 8]);
742 }
743
744 #[test]
747 fn build_ithmb_file_rgb565() {
748 let profile = Profile {
749 prefix: 0x1234_5678,
750 width: 1,
751 height: 1,
752 encoding: Encoding::Rgb565,
753 frame_byte_length: 2,
754 ..Default::default()
755 };
756 let bgra = vec![255, 255, 255, 255];
757 let file = build_ithmb_file(&bgra, 1, 1, &profile);
758 assert_eq!(file.len(), 6);
760 assert_eq!(&file[0..4], &[0x12, 0x34, 0x56, 0x78]);
761 assert_eq!(&file[4..6], &[0xFF, 0xFF]);
762 }
763
764 #[test]
765 fn build_ithmb_file_padded() {
766 let profile = Profile {
767 prefix: 0x0000_1001,
768 width: 1,
769 height: 1,
770 encoding: Encoding::Rgb565,
771 frame_byte_length: 100,
772 is_padded: true,
773 slot_size: 100,
774 ..Default::default()
775 };
776 let bgra = vec![255, 255, 255, 255];
777 let file = build_ithmb_file(&bgra, 1, 1, &profile);
778 assert_eq!(file.len(), 104);
780 assert_eq!(&file[0..4], &[0x00, 0x00, 0x10, 0x01]);
781 assert_eq!(&file[4..6], &[0xFF, 0xFF]);
782 }
783}