1use bytes::Bytes;
21use image::codecs::jpeg::JpegEncoder;
22use image::{DynamicImage, ImageReader};
23use jpeg2k::Image as J2kImage;
24use std::io::Cursor;
25
26use crate::error::TileError;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34enum TileFormat {
35 Jpeg,
37 Jpeg2000,
39 Unknown,
41}
42
43fn detect_tile_format(data: &[u8]) -> TileFormat {
51 if data.len() < 2 {
52 return TileFormat::Unknown;
53 }
54
55 if data[0] == 0xFF && data[1] == 0xD8 {
57 return TileFormat::Jpeg;
58 }
59
60 if data.len() >= 4 && data[0..4] == [0xFF, 0x4F, 0xFF, 0x51] {
62 return TileFormat::Jpeg2000;
63 }
64
65 if data.len() >= 12 && &data[4..8] == b"jP " {
68 return TileFormat::Jpeg2000;
69 }
70
71 TileFormat::Unknown
72}
73
74fn decode_jpeg2000(data: &[u8]) -> Result<DynamicImage, TileError> {
82 let j2k_image = J2kImage::from_bytes(data).map_err(|e| TileError::DecodeError {
83 message: format!("JPEG 2000 decode error: {}", e),
84 })?;
85
86 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
90 DynamicImage::try_from(&j2k_image)
91 }));
92
93 match result {
94 Ok(Ok(img)) => Ok(img),
95 Ok(Err(e)) => Err(TileError::DecodeError {
96 message: format!("JPEG 2000 to DynamicImage conversion error: {}", e),
97 }),
98 Err(_) => {
99 decode_jpeg2000_manual(&j2k_image)
101 }
102 }
103}
104
105fn decode_jpeg2000_manual(j2k_image: &J2kImage) -> Result<DynamicImage, TileError> {
110 let num_components = j2k_image.num_components();
111 let components = j2k_image.components();
112
113 if components.is_empty() {
114 return Err(TileError::DecodeError {
115 message: "JPEG 2000 image has no components".to_string(),
116 });
117 }
118
119 match num_components {
120 1 => {
121 let comp = &components[0];
123 let comp_data = comp.data();
124 let comp_width = comp.width();
125 let comp_height = comp.height();
126
127 let pixels: Vec<u8> = comp_data.iter().map(|&v| v.clamp(0, 255) as u8).collect();
129
130 image::GrayImage::from_raw(comp_width, comp_height, pixels)
131 .map(DynamicImage::ImageLuma8)
132 .ok_or_else(|| TileError::DecodeError {
133 message: format!(
134 "Failed to create grayscale image from components: {}x{}",
135 comp_width, comp_height
136 ),
137 })
138 }
139 3 => {
140 let y_comp = &components[0];
142 let cb_comp = &components[1];
143 let cr_comp = &components[2];
144
145 let y_width = y_comp.width();
147 let y_height = y_comp.height();
148 let cb_width = cb_comp.width();
149 let cb_height = cb_comp.height();
150
151 if cb_width == y_width && cb_height == y_height {
152 let y_data = y_comp.data();
154 let cb_data = cb_comp.data();
155 let cr_data = cr_comp.data();
156
157 let mut pixels = Vec::with_capacity((y_width * y_height * 3) as usize);
158 for i in 0..(y_width * y_height) as usize {
159 pixels.push(y_data[i].clamp(0, 255) as u8);
160 pixels.push(cb_data[i].clamp(0, 255) as u8);
161 pixels.push(cr_data[i].clamp(0, 255) as u8);
162 }
163
164 image::RgbImage::from_raw(y_width, y_height, pixels)
165 .map(DynamicImage::ImageRgb8)
166 .ok_or_else(|| TileError::DecodeError {
167 message: format!(
168 "Failed to create RGB image from components: {}x{}",
169 y_width, y_height
170 ),
171 })
172 } else {
173 let y_data = y_comp.data();
175 let cb_data = cb_comp.data();
176 let cr_data = cr_comp.data();
177
178 let mut pixels = Vec::with_capacity((y_width * y_height * 3) as usize);
179
180 for y_row in 0..y_height {
181 for y_col in 0..y_width {
182 let y_idx = (y_row * y_width + y_col) as usize;
183
184 let cb_col = (y_col * cb_width) / y_width;
186 let cb_row = (y_row * cb_height) / y_height;
187 let cb_idx = (cb_row * cb_width + cb_col) as usize;
188
189 let y_val = y_data[y_idx] as f32;
190 let cb_val = cb_data.get(cb_idx).copied().unwrap_or(128) as f32 - 128.0;
191 let cr_val = cr_data.get(cb_idx).copied().unwrap_or(128) as f32 - 128.0;
192
193 let r = (y_val + 1.402 * cr_val).clamp(0.0, 255.0) as u8;
195 let g =
196 (y_val - 0.344136 * cb_val - 0.714136 * cr_val).clamp(0.0, 255.0) as u8;
197 let b = (y_val + 1.772 * cb_val).clamp(0.0, 255.0) as u8;
198
199 pixels.push(r);
200 pixels.push(g);
201 pixels.push(b);
202 }
203 }
204
205 image::RgbImage::from_raw(y_width, y_height, pixels)
206 .map(DynamicImage::ImageRgb8)
207 .ok_or_else(|| TileError::DecodeError {
208 message: format!(
209 "Failed to create RGB image from YCbCr components: {}x{}",
210 y_width, y_height
211 ),
212 })
213 }
214 }
215 _ => Err(TileError::DecodeError {
216 message: format!(
217 "Unsupported JPEG 2000 component count: {} (expected 1 or 3)",
218 num_components
219 ),
220 }),
221 }
222}
223
224pub const DEFAULT_JPEG_QUALITY: u8 = 80;
226
227pub const MIN_JPEG_QUALITY: u8 = 1;
229
230pub const MAX_JPEG_QUALITY: u8 = 100;
232
233#[derive(Debug, Clone, Default)]
257pub struct JpegTileEncoder {
258 }
261
262impl JpegTileEncoder {
263 pub fn new() -> Self {
265 Self {}
266 }
267
268 pub fn encode(&self, source: &[u8], quality: u8) -> Result<Bytes, TileError> {
289 let quality = quality.clamp(MIN_JPEG_QUALITY, MAX_JPEG_QUALITY);
291
292 let format = detect_tile_format(source);
294
295 let img = match format {
296 TileFormat::Jpeg => {
297 let cursor = Cursor::new(source);
298 let reader = ImageReader::with_format(cursor, image::ImageFormat::Jpeg);
299 reader.decode().map_err(|e| TileError::DecodeError {
300 message: format!("JPEG decode error: {}", e),
301 })?
302 }
303 TileFormat::Jpeg2000 => decode_jpeg2000(source)?,
304 TileFormat::Unknown => {
305 return Err(TileError::DecodeError {
306 message: "Unknown tile format: expected JPEG or JPEG 2000".to_string(),
307 });
308 }
309 };
310
311 let mut output = Vec::new();
313 let mut encoder = JpegEncoder::new_with_quality(&mut output, quality);
314
315 encoder
316 .encode_image(&img)
317 .map_err(|e| TileError::EncodeError {
318 message: e.to_string(),
319 })?;
320
321 Ok(Bytes::from(output))
322 }
323
324 pub fn encode_default(&self, source: &[u8]) -> Result<Bytes, TileError> {
328 self.encode(source, DEFAULT_JPEG_QUALITY)
329 }
330
331 pub fn dimensions(&self, source: &[u8]) -> Result<(u32, u32), TileError> {
341 let format = detect_tile_format(source);
342
343 match format {
344 TileFormat::Jpeg => {
345 let cursor = Cursor::new(source);
346 let reader = ImageReader::with_format(cursor, image::ImageFormat::Jpeg);
347 reader
348 .into_dimensions()
349 .map_err(|e| TileError::DecodeError {
350 message: format!("JPEG dimensions error: {}", e),
351 })
352 }
353 TileFormat::Jpeg2000 => {
354 let j2k = J2kImage::from_bytes(source).map_err(|e| TileError::DecodeError {
356 message: format!("JPEG 2000 decode error: {}", e),
357 })?;
358 Ok((j2k.width(), j2k.height()))
359 }
360 TileFormat::Unknown => Err(TileError::DecodeError {
361 message: "Unknown tile format: expected JPEG or JPEG 2000".to_string(),
362 }),
363 }
364 }
365}
366
367#[inline]
375pub fn is_valid_quality(quality: u8) -> bool {
376 (MIN_JPEG_QUALITY..=MAX_JPEG_QUALITY).contains(&quality)
377}
378
379#[inline]
383pub fn clamp_quality(quality: u8) -> u8 {
384 quality.clamp(MIN_JPEG_QUALITY, MAX_JPEG_QUALITY)
385}
386
387#[cfg(test)]
392mod tests {
393 use super::*;
394
395 fn create_test_jpeg() -> Vec<u8> {
396 use image::{GrayImage, Luma};
398
399 let img = GrayImage::from_fn(8, 8, |x, y| {
400 let val = ((x + y) * 16) as u8;
401 Luma([val])
402 });
403
404 let mut buf = Vec::new();
405 let mut encoder = JpegEncoder::new_with_quality(&mut buf, 90);
406 encoder.encode_image(&img).unwrap();
407 buf
408 }
409
410 #[test]
411 fn test_encoder_creation() {
412 let encoder = JpegTileEncoder::new();
413 let _ = &encoder;
415 }
416
417 #[test]
418 fn test_encode_valid_jpeg() {
419 let encoder = JpegTileEncoder::new();
420 let source = create_test_jpeg();
421
422 let result = encoder.encode(&source, 80);
423 assert!(result.is_ok());
424
425 let output = result.unwrap();
426 assert!(output.len() >= 2);
428 assert_eq!(output[0], 0xFF);
429 assert_eq!(output[1], 0xD8);
430 }
431
432 #[test]
433 fn test_encode_different_qualities() {
434 let encoder = JpegTileEncoder::new();
435 let source = create_test_jpeg();
436
437 let low_quality = encoder.encode(&source, 10).unwrap();
438 let high_quality = encoder.encode(&source, 95).unwrap();
439
440 assert!(!low_quality.is_empty());
443 assert!(!high_quality.is_empty());
444 }
445
446 #[test]
447 fn test_encode_default() {
448 let encoder = JpegTileEncoder::new();
449 let source = create_test_jpeg();
450
451 let result = encoder.encode_default(&source);
452 assert!(result.is_ok());
453 }
454
455 #[test]
456 fn test_encode_invalid_data() {
457 let encoder = JpegTileEncoder::new();
458 let invalid = vec![0x00, 0x01, 0x02, 0x03];
459
460 let result = encoder.encode(&invalid, 80);
461 assert!(result.is_err());
462
463 match result {
464 Err(TileError::DecodeError { .. }) => {}
465 _ => panic!("Expected DecodeError"),
466 }
467 }
468
469 #[test]
470 fn test_encode_empty_data() {
471 let encoder = JpegTileEncoder::new();
472
473 let result = encoder.encode(&[], 80);
474 assert!(result.is_err());
475 }
476
477 #[test]
478 fn test_quality_clamping() {
479 let encoder = JpegTileEncoder::new();
480 let source = create_test_jpeg();
481
482 let result = encoder.encode(&source, 0);
484 assert!(result.is_ok());
485
486 let result = encoder.encode(&source, 255);
488 assert!(result.is_ok());
489 }
490
491 #[test]
492 fn test_dimensions() {
493 let encoder = JpegTileEncoder::new();
494 let source = create_test_jpeg();
495
496 let (width, height) = encoder.dimensions(&source).unwrap();
497 assert_eq!(width, 8);
498 assert_eq!(height, 8);
499 }
500
501 #[test]
502 fn test_dimensions_invalid() {
503 let encoder = JpegTileEncoder::new();
504 let invalid = vec![0x00, 0x01, 0x02];
505
506 let result = encoder.dimensions(&invalid);
507 assert!(result.is_err());
508 }
509
510 #[test]
511 fn test_is_valid_quality() {
512 assert!(!is_valid_quality(0));
513 assert!(is_valid_quality(1));
514 assert!(is_valid_quality(50));
515 assert!(is_valid_quality(100));
516 assert!(!is_valid_quality(101));
517 }
518
519 #[test]
520 fn test_clamp_quality() {
521 assert_eq!(clamp_quality(0), 1);
522 assert_eq!(clamp_quality(1), 1);
523 assert_eq!(clamp_quality(50), 50);
524 assert_eq!(clamp_quality(100), 100);
525 assert_eq!(clamp_quality(150), 100);
526 assert_eq!(clamp_quality(255), 100);
527 }
528
529 #[test]
530 fn test_output_is_valid_jpeg() {
531 let encoder = JpegTileEncoder::new();
532 let source = create_test_jpeg();
533
534 let output = encoder.encode(&source, 80).unwrap();
535
536 assert_eq!(output[0], 0xFF); assert_eq!(output[1], 0xD8);
539 assert_eq!(output[output.len() - 2], 0xFF); assert_eq!(output[output.len() - 1], 0xD9);
541
542 let result = encoder.dimensions(&output);
544 assert!(result.is_ok());
545 }
546
547 #[test]
552 fn test_detect_jpeg_format() {
553 let jpeg = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10];
554 assert_eq!(detect_tile_format(&jpeg), TileFormat::Jpeg);
555 }
556
557 #[test]
558 fn test_detect_j2k_codestream_format() {
559 let j2k = [0xFF, 0x4F, 0xFF, 0x51, 0x00, 0x00];
561 assert_eq!(detect_tile_format(&j2k), TileFormat::Jpeg2000);
562 }
563
564 #[test]
565 fn test_detect_jp2_container_format() {
566 let jp2 = [
568 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A, ];
572 assert_eq!(detect_tile_format(&jp2), TileFormat::Jpeg2000);
573 }
574
575 #[test]
576 fn test_detect_unknown_format() {
577 let unknown = [0x00, 0x00, 0x00, 0x00];
578 assert_eq!(detect_tile_format(&unknown), TileFormat::Unknown);
579 }
580
581 #[test]
582 fn test_detect_empty_data() {
583 assert_eq!(detect_tile_format(&[]), TileFormat::Unknown);
584 }
585
586 #[test]
587 fn test_detect_short_data() {
588 assert_eq!(detect_tile_format(&[0xFF]), TileFormat::Unknown);
589 }
590
591 #[test]
592 fn test_encode_unknown_format_returns_error() {
593 let encoder = JpegTileEncoder::new();
594 let unknown = vec![0x00, 0x01, 0x02, 0x03];
595
596 let result = encoder.encode(&unknown, 80);
597 assert!(result.is_err());
598
599 match result {
600 Err(TileError::DecodeError { message }) => {
601 assert!(message.contains("Unknown tile format"));
602 }
603 _ => panic!("Expected DecodeError with format message"),
604 }
605 }
606}