From ae5098083e888d135775e540a2d485156c0a1e5d Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Tue, 20 May 2025 17:34:26 +0100 Subject: [PATCH 1/5] Add `CompressionValue` --- libraries/rawkit/src/decoder/arw2.rs | 4 +- libraries/rawkit/src/decoder/uncompressed.rs | 3 +- libraries/rawkit/src/lib.rs | 4 +- libraries/rawkit/src/tiff/mod.rs | 6 +-- libraries/rawkit/src/tiff/tags.rs | 4 +- libraries/rawkit/src/tiff/types.rs | 11 +++- libraries/rawkit/src/tiff/values.rs | 54 ++++++++++++++++++++ 7 files changed, 75 insertions(+), 11 deletions(-) diff --git a/libraries/rawkit/src/decoder/arw2.rs b/libraries/rawkit/src/decoder/arw2.rs index 676880f1fb..8daea7670a 100644 --- a/libraries/rawkit/src/decoder/arw2.rs +++ b/libraries/rawkit/src/decoder/arw2.rs @@ -1,6 +1,6 @@ use crate::tiff::file::{Endian, TiffRead}; use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels}; -use crate::tiff::values::CurveLookupTable; +use crate::tiff::values::{CompressionValue, CurveLookupTable}; use crate::tiff::{Ifd, TiffError}; use crate::{RawImage, SubtractBlack, Transform}; use rawkit_proc_macros::Tag; @@ -26,7 +26,7 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len()); assert!(ifd.strip_offsets.len() == 1); - assert!(ifd.compression == 32767); + assert!(ifd.compression == CompressionValue::Sony_ARW_Compressed); let image_width: usize = ifd.image_width.try_into().unwrap(); let image_height: usize = ifd.image_height.try_into().unwrap(); diff --git a/libraries/rawkit/src/decoder/uncompressed.rs b/libraries/rawkit/src/decoder/uncompressed.rs index cd8ca51f52..8ecdf0f701 100644 --- a/libraries/rawkit/src/decoder/uncompressed.rs +++ b/libraries/rawkit/src/decoder/uncompressed.rs @@ -1,5 +1,6 @@ use crate::tiff::file::TiffRead; use crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels}; +use crate::tiff::values::CompressionValue; use crate::tiff::{Ifd, TiffError}; use crate::{RawImage, SubtractBlack, Transform}; use rawkit_proc_macros::Tag; @@ -26,7 +27,7 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len()); assert!(ifd.strip_offsets.len() == 1); - assert!(ifd.compression == 1); // 1 is the value for uncompressed format + assert!(ifd.compression == CompressionValue::Uncompressed); let image_width: usize = ifd.image_width.try_into().unwrap(); let image_height: usize = ifd.image_height.try_into().unwrap(); diff --git a/libraries/rawkit/src/lib.rs b/libraries/rawkit/src/lib.rs index d20c5f9b40..8480f5a178 100644 --- a/libraries/rawkit/src/lib.rs +++ b/libraries/rawkit/src/lib.rs @@ -13,7 +13,7 @@ use std::io::{Read, Seek}; use thiserror::Error; use tiff::file::TiffRead; use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag}; -use tiff::values::Transform; +use tiff::values::{CompressionValue, Transform}; use tiff::{Ifd, TiffError}; pub(crate) const CHANNELS_IN_RGB: usize = 3; @@ -127,7 +127,7 @@ impl RawImage { let sub_ifd = ifd.get_value::(&mut file)?; let arw_ifd = sub_ifd.get_value::(&mut file)?; - if arw_ifd.compression == 1 { + if arw_ifd.compression == CompressionValue::Uncompressed { decoder::uncompressed::decode(sub_ifd, &mut file) } else if arw_ifd.strip_byte_counts[0] == arw_ifd.image_width * arw_ifd.image_height { decoder::arw2::decode(sub_ifd, &mut file) diff --git a/libraries/rawkit/src/tiff/mod.rs b/libraries/rawkit/src/tiff/mod.rs index f9a366a2b0..829f79c891 100644 --- a/libraries/rawkit/src/tiff/mod.rs +++ b/libraries/rawkit/src/tiff/mod.rs @@ -88,10 +88,10 @@ impl Ifd { } file.seek_from_start(offset)?; - let num = file.read_u16()?; + let num_entries = file.read_u16()?; - let mut ifd_entries = Vec::with_capacity(num.into()); - for _ in 0..num { + let mut ifd_entries = Vec::with_capacity(num_entries.into()); + for _ in 0..num_entries { let tag = file.read_u16()?.into(); let the_type = file.read_u16()?.into(); let count = file.read_u32()?; diff --git a/libraries/rawkit/src/tiff/tags.rs b/libraries/rawkit/src/tiff/tags.rs index 76eebb9181..3d78453153 100644 --- a/libraries/rawkit/src/tiff/tags.rs +++ b/libraries/rawkit/src/tiff/tags.rs @@ -1,4 +1,4 @@ -use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeOrientation, TypeSRational, TypeSShort, TypeShort, TypeSonyToneCurve, TypeString}; +use super::types::{Array, ConstArray, TagType, TypeByte, TypeCompression, TypeIfd, TypeLong, TypeNumber, TypeOrientation, TypeSRational, TypeSShort, TypeShort, TypeSonyToneCurve, TypeString}; use super::{Ifd, TagId, TiffError, TiffRead}; use std::io::{Read, Seek}; @@ -55,7 +55,7 @@ impl SimpleTag for BitsPerSample { } impl SimpleTag for Compression { - type Type = TypeShort; + type Type = TypeCompression; const ID: TagId = TagId::Compression; const NAME: &'static str = "Compression"; diff --git a/libraries/rawkit/src/tiff/types.rs b/libraries/rawkit/src/tiff/types.rs index 1bac61a79f..525a24f1aa 100644 --- a/libraries/rawkit/src/tiff/types.rs +++ b/libraries/rawkit/src/tiff/types.rs @@ -1,5 +1,5 @@ use super::file::TiffRead; -use super::values::{CurveLookupTable, Rational, Transform}; +use super::values::{CompressionValue, CurveLookupTable, Rational, Transform}; use super::{Ifd, IfdTagType, TiffError}; use std::io::{Read, Seek}; @@ -350,6 +350,7 @@ impl TagType for ConstArray { } } +pub struct TypeCompression; pub struct TypeString; pub struct TypeSonyToneCurve; pub struct TypeOrientation; @@ -392,3 +393,11 @@ impl TagType for TypeOrientation { }) } } + +impl TagType for TypeCompression { + type Output = CompressionValue; + + fn read(file: &mut TiffRead) -> Result { + Ok(CompressionValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)?) + } +} \ No newline at end of file diff --git a/libraries/rawkit/src/tiff/values.rs b/libraries/rawkit/src/tiff/values.rs index 01e4498103..184e79e111 100644 --- a/libraries/rawkit/src/tiff/values.rs +++ b/libraries/rawkit/src/tiff/values.rs @@ -1,3 +1,5 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; + pub trait ToFloat { fn to_float(&self) -> f64; } @@ -77,3 +79,55 @@ impl Transform { } } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] +#[repr(u16)] +#[allow(non_camel_case_types)] +pub enum CompressionValue { + Uncompressed = 1, + CCITT_1D = 2, + T4 = 3, + T6 = 4, + LZW = 5, + JPEG_Old = 6, + JPEG = 7, + AdobeDeflate = 8, + JBIG_BW = 9, + JBIG_Color = 10, + KODAK_626 = 262, + Next = 32766, + Sony_ARW_Compressed = 32767, + Packed_Raw = 32769, + Samsung_SRW_Compressed = 32770, + CCIRLEW = 32771, + Samsung_SRW_Compressed_2 = 32772, + PackedBits = 32773, + Thunderscan = 32809, + Kodak_KDC_Compressed = 32867, + IT8CTPAD = 32895, + IT8LW = 32896, + IT8MP = 32897, + IT8BL = 32898, + PixarFilm = 32908, + PixarLog = 32909, + Deflate = 32946, + DCS = 32947, + AperioJPEG2K_YCbCr = 33003, + AperioJPEG2K_RGB = 33005, + JBIG = 34661, + SGILog = 34676, + SGILog24 = 34677, + JPEG2K = 34712, + NikonNEFCompressed = 34713, + JBIG2_TIFF_FX = 34715, + ESRI_Lerc = 34887, + LossyJPEG = 34892, + LZMA2 = 34925, + PNG = 34933, + JPEG_XR = 34934, + Zstd = 50000, + WebP = 50001, + JPEG_XL = 52546, + Kodak_DCR_Compressed = 65000, + Pentax_PEF_Compressed = 65535, +} \ No newline at end of file From cd0b11903f6444b0a86115fb66ddd2a1f2f18370 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Tue, 20 May 2025 17:51:14 +0100 Subject: [PATCH 2/5] Rename `Transform` to `OrientationValue` --- libraries/rawkit/src/decoder/arw1.rs | 4 +-- libraries/rawkit/src/decoder/arw2.rs | 4 +-- libraries/rawkit/src/decoder/uncompressed.rs | 4 +-- libraries/rawkit/src/lib.rs | 22 ++++++------- .../rawkit/src/postprocessing/transform.rs | 32 +++++++++---------- libraries/rawkit/src/tiff/types.rs | 16 ++-------- libraries/rawkit/src/tiff/values.rs | 31 +++++++++--------- 7 files changed, 51 insertions(+), 62 deletions(-) diff --git a/libraries/rawkit/src/decoder/arw1.rs b/libraries/rawkit/src/decoder/arw1.rs index 7bf6682546..e53e9b85a5 100644 --- a/libraries/rawkit/src/decoder/arw1.rs +++ b/libraries/rawkit/src/decoder/arw1.rs @@ -1,7 +1,7 @@ use crate::tiff::Ifd; use crate::tiff::file::TiffRead; use crate::tiff::tags::SonyDataOffset; -use crate::{RawImage, SubtractBlack, Transform}; +use crate::{RawImage, SubtractBlack, OrientationValue}; use bitstream_io::{BE, BitRead, BitReader, Endianness}; use std::io::{Read, Seek}; @@ -25,7 +25,7 @@ pub fn decode_a100(ifd: Ifd, file: &mut TiffRead) -> RawImage #[allow(unreachable_code)] maximum: (1 << 12) - 1, black: SubtractBlack::None, - transform: Transform::Horizontal, + orientation: OrientationValue::Horizontal, camera_model: None, camera_white_balance: None, white_balance: None, diff --git a/libraries/rawkit/src/decoder/arw2.rs b/libraries/rawkit/src/decoder/arw2.rs index 8daea7670a..77beadea3a 100644 --- a/libraries/rawkit/src/decoder/arw2.rs +++ b/libraries/rawkit/src/decoder/arw2.rs @@ -2,7 +2,7 @@ use crate::tiff::file::{Endian, TiffRead}; use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels}; use crate::tiff::values::{CompressionValue, CurveLookupTable}; use crate::tiff::{Ifd, TiffError}; -use crate::{RawImage, SubtractBlack, Transform}; +use crate::{RawImage, SubtractBlack, OrientationValue}; use rawkit_proc_macros::Tag; use std::io::{Read, Seek}; @@ -49,7 +49,7 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { cfa_pattern: ifd.cfa_pattern.try_into().unwrap(), maximum: (1 << 14) - 1, black: SubtractBlack::CfaGrid([512, 512, 512, 512]), // TODO: Find the correct way to do this - transform: Transform::Horizontal, + orientation: OrientationValue::Horizontal, camera_model: None, camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)), white_balance: None, diff --git a/libraries/rawkit/src/decoder/uncompressed.rs b/libraries/rawkit/src/decoder/uncompressed.rs index 8ecdf0f701..e836b16f13 100644 --- a/libraries/rawkit/src/decoder/uncompressed.rs +++ b/libraries/rawkit/src/decoder/uncompressed.rs @@ -2,7 +2,7 @@ use crate::tiff::file::TiffRead; use crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels}; use crate::tiff::values::CompressionValue; use crate::tiff::{Ifd, TiffError}; -use crate::{RawImage, SubtractBlack, Transform}; +use crate::{RawImage, SubtractBlack, OrientationValue}; use rawkit_proc_macros::Tag; use std::io::{Read, Seek}; @@ -58,7 +58,7 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { cfa_pattern: ifd.cfa_pattern.try_into().unwrap(), maximum: if bits_per_sample == 16 { u16::MAX } else { (1 << bits_per_sample) - 1 }, black: SubtractBlack::CfaGrid(ifd.black_level), - transform: Transform::Horizontal, + orientation: OrientationValue::Horizontal, camera_model: None, camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)), white_balance: None, diff --git a/libraries/rawkit/src/lib.rs b/libraries/rawkit/src/lib.rs index 8480f5a178..067e93ac42 100644 --- a/libraries/rawkit/src/lib.rs +++ b/libraries/rawkit/src/lib.rs @@ -13,7 +13,7 @@ use std::io::{Read, Seek}; use thiserror::Error; use tiff::file::TiffRead; use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag}; -use tiff::values::{CompressionValue, Transform}; +use tiff::values::{CompressionValue, OrientationValue}; use tiff::{Ifd, TiffError}; pub(crate) const CHANNELS_IN_RGB: usize = 3; @@ -48,7 +48,7 @@ pub struct RawImage { pub cfa_pattern: [u8; 4], /// Transformation to be applied to negate the orientation of camera. - pub transform: Transform, + pub orientation: OrientationValue, /// The maximum possible value of pixel that the camera sensor could give. pub maximum: u16, @@ -97,8 +97,8 @@ pub struct Image { /// The transformation required to orient the image correctly. /// - /// This will be [`Transform::Horizontal`] after the transform step is applied. - pub transform: Transform, + /// This will be [`OrientationValue::Horizontal`] after the orientation step is applied. + pub orientation: OrientationValue, } #[allow(dead_code)] @@ -119,7 +119,7 @@ impl RawImage { let ifd = Ifd::new_first_ifd(&mut file)?; let camera_model = metadata::identify::identify_camera_model(&ifd, &mut file).unwrap(); - let transform = ifd.get_value::(&mut file)?; + let orientation = ifd.get_value::(&mut file)?; let mut raw_image = if camera_model.model == "DSLR-A100" { decoder::arw1::decode_a100(ifd, &mut file) @@ -138,7 +138,7 @@ impl RawImage { }; raw_image.camera_model = Some(camera_model); - raw_image.transform = transform; + raw_image.orientation = orientation; raw_image.calculate_conversion_matrices(); @@ -156,7 +156,7 @@ impl RawImage { data: image.data.iter().map(|x| (x >> 8) as u8).collect(), width: image.width, height: image.height, - transform: image.transform, + orientation: image.orientation, } } @@ -174,7 +174,7 @@ impl RawImage { let image = raw_image.demosaic_and_apply((convert_to_rgb, &mut record_histogram)); let gamma_correction = image.gamma_correction_fn(&record_histogram.histogram); - if image.transform == Transform::Horizontal { + if image.orientation == OrientationValue::Horizontal { image.apply(gamma_correction) } else { image.transform_and_apply(gamma_correction) @@ -211,7 +211,7 @@ impl RawImage { data: image, width: self.width, height: self.height, - transform: self.transform, + orientation: self.orientation, } } } @@ -232,7 +232,7 @@ impl Image { pub fn transform_and_apply(self, mut transform: impl PixelTransform) -> Image { let mut image = vec![0; self.width * self.height * 3]; - let (width, height, iter) = self.transform_iter(); + let (width, height, iter) = self.orientation_iter(); for Pixel { values, row, column } in iter.map(|mut pixel| { pixel.values = transform.apply(pixel); pixel @@ -246,7 +246,7 @@ impl Image { data: image, width, height, - transform: Transform::Horizontal, + orientation: OrientationValue::Horizontal, } } } diff --git a/libraries/rawkit/src/postprocessing/transform.rs b/libraries/rawkit/src/postprocessing/transform.rs index 5ffe495712..efc37d2ea4 100644 --- a/libraries/rawkit/src/postprocessing/transform.rs +++ b/libraries/rawkit/src/postprocessing/transform.rs @@ -1,16 +1,16 @@ -use crate::{Image, Pixel, Transform}; +use crate::{Image, Pixel, OrientationValue}; impl Image { - pub fn transform_iter(&self) -> (usize, usize, impl Iterator + use<'_>) { - let (final_width, final_height) = if self.transform.will_swap_coordinates() { + pub fn orientation_iter(&self) -> (usize, usize, impl Iterator + use<'_>) { + let (final_width, final_height) = if self.orientation.will_swap_coordinates() { (self.height, self.width) } else { (self.width, self.height) }; - let index_0_0 = inverse_transform_index(self.transform, 0, 0, self.width, self.height); - let index_0_1 = inverse_transform_index(self.transform, 0, 1, self.width, self.height); - let index_1_0 = inverse_transform_index(self.transform, 1, 0, self.width, self.height); + let index_0_0 = inverse_orientation_index(self.orientation, 0, 0, self.width, self.height); + let index_0_1 = inverse_orientation_index(self.orientation, 0, 1, self.width, self.height); + let index_1_0 = inverse_orientation_index(self.orientation, 1, 0, self.width, self.height); let column_step = (index_0_1.0 - index_0_0.0, index_0_1.1 - index_0_0.1); let row_step = (index_1_0.0 - index_0_0.0, index_1_0.1 - index_0_0.1); @@ -42,16 +42,16 @@ impl Image { } } -pub fn inverse_transform_index(transform: Transform, mut row: usize, mut column: usize, width: usize, height: usize) -> (i64, i64) { - let value = match transform { - Transform::Horizontal => 0, - Transform::MirrorHorizontal => 1, - Transform::Rotate180 => 3, - Transform::MirrorVertical => 2, - Transform::MirrorHorizontalRotate270 => 4, - Transform::Rotate90 => 6, - Transform::MirrorHorizontalRotate90 => 7, - Transform::Rotate270 => 5, +pub fn inverse_orientation_index(orientation: OrientationValue, mut row: usize, mut column: usize, width: usize, height: usize) -> (i64, i64) { + let value = match orientation { + OrientationValue::Horizontal => 0, + OrientationValue::MirrorHorizontal => 1, + OrientationValue::Rotate180 => 3, + OrientationValue::MirrorVertical => 2, + OrientationValue::MirrorHorizontalRotate270 => 4, + OrientationValue::Rotate90 => 6, + OrientationValue::MirrorHorizontalRotate90 => 7, + OrientationValue::Rotate270 => 5, }; if value & 4 != 0 { diff --git a/libraries/rawkit/src/tiff/types.rs b/libraries/rawkit/src/tiff/types.rs index 525a24f1aa..f32a3570b0 100644 --- a/libraries/rawkit/src/tiff/types.rs +++ b/libraries/rawkit/src/tiff/types.rs @@ -1,5 +1,5 @@ use super::file::TiffRead; -use super::values::{CompressionValue, CurveLookupTable, Rational, Transform}; +use super::values::{CompressionValue, CurveLookupTable, Rational, OrientationValue}; use super::{Ifd, IfdTagType, TiffError}; use std::io::{Read, Seek}; @@ -377,20 +377,10 @@ impl TagType for TypeSonyToneCurve { } impl TagType for TypeOrientation { - type Output = Transform; + type Output = OrientationValue; fn read(file: &mut TiffRead) -> Result { - Ok(match TypeShort::read(file)? { - 1 => Transform::Horizontal, - 2 => Transform::MirrorHorizontal, - 3 => Transform::Rotate180, - 4 => Transform::MirrorVertical, - 5 => Transform::MirrorHorizontalRotate270, - 6 => Transform::Rotate90, - 7 => Transform::MirrorHorizontalRotate90, - 8 => Transform::Rotate270, - _ => return Err(TiffError::InvalidValue), - }) + Ok(OrientationValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)?) } } diff --git a/libraries/rawkit/src/tiff/values.rs b/libraries/rawkit/src/tiff/values.rs index 184e79e111..0bf2d157d0 100644 --- a/libraries/rawkit/src/tiff/values.rs +++ b/libraries/rawkit/src/tiff/values.rs @@ -53,29 +53,28 @@ impl CurveLookupTable { } } -#[derive(Copy, Clone, Eq, PartialEq)] -pub enum Transform { - Horizontal, - MirrorHorizontal, - Rotate180, - MirrorVertical, - MirrorHorizontalRotate270, - Rotate90, - MirrorHorizontalRotate90, - Rotate270, +#[derive(Copy, Clone, Eq, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u16)] +pub enum OrientationValue { + Horizontal = 1, + MirrorHorizontal = 2, + Rotate180 = 3, + MirrorVertical = 4, + MirrorHorizontalRotate270 = 5, + Rotate90 = 6, + MirrorHorizontalRotate90 = 7, + Rotate270 = 8, } -impl Transform { +impl OrientationValue { pub fn is_identity(&self) -> bool { - *self == Transform::Horizontal + *self == Self::Horizontal } pub fn will_swap_coordinates(&self) -> bool { - use Transform as Tr; - match *self { - Tr::Horizontal | Tr::MirrorHorizontal | Tr::Rotate180 | Tr::MirrorVertical => false, - Tr::MirrorHorizontalRotate270 | Tr::Rotate90 | Tr::MirrorHorizontalRotate90 | Tr::Rotate270 => true, + Self::Horizontal | Self::MirrorHorizontal | Self::Rotate180 | Self::MirrorVertical => false, + Self::MirrorHorizontalRotate270 | Self::Rotate90 | Self::MirrorHorizontalRotate90 | Self::Rotate270 => true, } } } From ca07cf7f7de74f8bced1b151320c6c2f69de9779 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Tue, 20 May 2025 18:37:08 +0100 Subject: [PATCH 3/5] Add basic thumbnail extraction --- libraries/rawkit/src/lib.rs | 21 ++++++++++++++++++++- libraries/rawkit/src/tiff/mod.rs | 4 ++-- libraries/rawkit/src/tiff/tags.rs | 12 ++++++------ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/libraries/rawkit/src/lib.rs b/libraries/rawkit/src/lib.rs index 067e93ac42..45391c4839 100644 --- a/libraries/rawkit/src/lib.rs +++ b/libraries/rawkit/src/lib.rs @@ -12,13 +12,18 @@ use rawkit_proc_macros::Tag; use std::io::{Read, Seek}; use thiserror::Error; use tiff::file::TiffRead; -use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag}; +use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag, ThumbnailLength, ThumbnailOffset}; use tiff::values::{CompressionValue, OrientationValue}; use tiff::{Ifd, TiffError}; pub(crate) const CHANNELS_IN_RGB: usize = 3; pub(crate) type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB]; +/// A thumbnail image extracted from the raw file. This is usually a JPEG image. +pub struct ThumbnailImage { + pub data: Vec, +} + /// The amount of black level to be subtracted from Raw Image. pub enum SubtractBlack { /// Don't subtract any value. @@ -145,6 +150,20 @@ impl RawImage { Ok(raw_image) } + pub fn extract_thumbnail(reader: &mut R) -> Result { + let mut file = TiffRead::new(reader)?; + let ifd = Ifd::new_first_ifd(&mut file)?; + + let thumbnail_offset = ifd.get_value::(&mut file)?; + let thumbnail_length = ifd.get_value::(&mut file)?; + file.seek_from_start(thumbnail_offset)?; + + let mut thumbnail_data = vec![0; thumbnail_length as usize]; + file.read_exact(&mut thumbnail_data)?; + + Ok(ThumbnailImage { data: thumbnail_data }) + } + /// Converts the [`RawImage`] to an [`Image`] with 8 bit resolution for each channel. /// /// Applies all the processing steps to finally get RGB pixel data. diff --git a/libraries/rawkit/src/tiff/mod.rs b/libraries/rawkit/src/tiff/mod.rs index 829f79c891..1136e837fa 100644 --- a/libraries/rawkit/src/tiff/mod.rs +++ b/libraries/rawkit/src/tiff/mod.rs @@ -26,8 +26,8 @@ pub enum TagId { RowsPerStrip = 0x116, StripByteCounts = 0x117, SubIfd = 0x14a, - JpegOffset = 0x201, - JpegLength = 0x202, + ThumbnailOffset = 0x201, + ThumbnailLength = 0x202, SonyToneCurve = 0x7010, BlackLevel = 0x7310, WhiteBalanceRggbLevels = 0x7313, diff --git a/libraries/rawkit/src/tiff/tags.rs b/libraries/rawkit/src/tiff/tags.rs index 3d78453153..19b5b51533 100644 --- a/libraries/rawkit/src/tiff/tags.rs +++ b/libraries/rawkit/src/tiff/tags.rs @@ -22,8 +22,8 @@ pub struct SamplesPerPixel; pub struct RowsPerStrip; pub struct StripByteCounts; pub struct SubIfd; -pub struct JpegOffset; -pub struct JpegLength; +pub struct ThumbnailOffset; +pub struct ThumbnailLength; pub struct SonyDataOffset; pub struct SonyToneCurve; pub struct BlackLevel; @@ -124,17 +124,17 @@ impl SimpleTag for SubIfd { const NAME: &'static str = "SubIFD"; } -impl SimpleTag for JpegOffset { +impl SimpleTag for ThumbnailOffset { type Type = TypeLong; - const ID: TagId = TagId::JpegOffset; + const ID: TagId = TagId::ThumbnailOffset; const NAME: &'static str = "Jpeg Offset"; } -impl SimpleTag for JpegLength { +impl SimpleTag for ThumbnailLength { type Type = TypeLong; - const ID: TagId = TagId::JpegLength; + const ID: TagId = TagId::ThumbnailLength; const NAME: &'static str = "Jpeg Length"; } From 85228c1b37d3561a3a09b93b3b6579c47c6bc695 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Tue, 20 May 2025 18:49:59 +0100 Subject: [PATCH 4/5] Add `ThumbnailFormat` --- libraries/rawkit/src/lib.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/libraries/rawkit/src/lib.rs b/libraries/rawkit/src/lib.rs index 45391c4839..1dc4cc94fd 100644 --- a/libraries/rawkit/src/lib.rs +++ b/libraries/rawkit/src/lib.rs @@ -19,9 +19,15 @@ use tiff::{Ifd, TiffError}; pub(crate) const CHANNELS_IN_RGB: usize = 3; pub(crate) type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB]; +pub enum ThumbnailFormat { + Jpeg, + Unsupported, +} + /// A thumbnail image extracted from the raw file. This is usually a JPEG image. pub struct ThumbnailImage { pub data: Vec, + pub format: ThumbnailFormat, } /// The amount of black level to be subtracted from Raw Image. @@ -150,10 +156,12 @@ impl RawImage { Ok(raw_image) } + /// Extracts the thumbnail image from the raw file. pub fn extract_thumbnail(reader: &mut R) -> Result { let mut file = TiffRead::new(reader)?; let ifd = Ifd::new_first_ifd(&mut file)?; + // TODO: ARW files Store the thumbnail offset and length in the first IFD. Add support for other file types in the future. let thumbnail_offset = ifd.get_value::(&mut file)?; let thumbnail_length = ifd.get_value::(&mut file)?; file.seek_from_start(thumbnail_offset)?; @@ -161,7 +169,17 @@ impl RawImage { let mut thumbnail_data = vec![0; thumbnail_length as usize]; file.read_exact(&mut thumbnail_data)?; - Ok(ThumbnailImage { data: thumbnail_data }) + // Check the first two bytes to determine the format of the thumbnail. + // JPEG format starts with 0xFF, 0xD8. + if thumbnail_data[0..2] == [0xFF, 0xD8] { + return Ok(ThumbnailImage { + data: thumbnail_data, + format: ThumbnailFormat::Jpeg, + }); + } else { + Err(DecoderError::UnsupportedThumbnailFormat) + } + } /// Converts the [`RawImage`] to an [`Image`] with 8 bit resolution for each channel. @@ -278,4 +296,6 @@ pub enum DecoderError { ConversionError(#[from] std::num::TryFromIntError), #[error("An IO Error ocurred")] IoError(#[from] std::io::Error), + #[error("The thumbnail format is unsupported")] + UnsupportedThumbnailFormat, } From 6327bc365bd8a77113c1ae2c79f2f21036d7a245 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Tue, 20 May 2025 18:52:30 +0100 Subject: [PATCH 5/5] fmt + clippy fixes --- libraries/rawkit/src/decoder/arw1.rs | 2 +- libraries/rawkit/src/decoder/arw2.rs | 2 +- libraries/rawkit/src/decoder/uncompressed.rs | 2 +- libraries/rawkit/src/lib.rs | 5 ++--- libraries/rawkit/src/postprocessing/transform.rs | 2 +- libraries/rawkit/src/tiff/types.rs | 8 ++++---- libraries/rawkit/src/tiff/values.rs | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/libraries/rawkit/src/decoder/arw1.rs b/libraries/rawkit/src/decoder/arw1.rs index e53e9b85a5..2a2f1d08a3 100644 --- a/libraries/rawkit/src/decoder/arw1.rs +++ b/libraries/rawkit/src/decoder/arw1.rs @@ -1,7 +1,7 @@ use crate::tiff::Ifd; use crate::tiff::file::TiffRead; use crate::tiff::tags::SonyDataOffset; -use crate::{RawImage, SubtractBlack, OrientationValue}; +use crate::{OrientationValue, RawImage, SubtractBlack}; use bitstream_io::{BE, BitRead, BitReader, Endianness}; use std::io::{Read, Seek}; diff --git a/libraries/rawkit/src/decoder/arw2.rs b/libraries/rawkit/src/decoder/arw2.rs index 77beadea3a..f08bd32e02 100644 --- a/libraries/rawkit/src/decoder/arw2.rs +++ b/libraries/rawkit/src/decoder/arw2.rs @@ -2,7 +2,7 @@ use crate::tiff::file::{Endian, TiffRead}; use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels}; use crate::tiff::values::{CompressionValue, CurveLookupTable}; use crate::tiff::{Ifd, TiffError}; -use crate::{RawImage, SubtractBlack, OrientationValue}; +use crate::{OrientationValue, RawImage, SubtractBlack}; use rawkit_proc_macros::Tag; use std::io::{Read, Seek}; diff --git a/libraries/rawkit/src/decoder/uncompressed.rs b/libraries/rawkit/src/decoder/uncompressed.rs index e836b16f13..cb0c1b6c84 100644 --- a/libraries/rawkit/src/decoder/uncompressed.rs +++ b/libraries/rawkit/src/decoder/uncompressed.rs @@ -2,7 +2,7 @@ use crate::tiff::file::TiffRead; use crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels}; use crate::tiff::values::CompressionValue; use crate::tiff::{Ifd, TiffError}; -use crate::{RawImage, SubtractBlack, OrientationValue}; +use crate::{OrientationValue, RawImage, SubtractBlack}; use rawkit_proc_macros::Tag; use std::io::{Read, Seek}; diff --git a/libraries/rawkit/src/lib.rs b/libraries/rawkit/src/lib.rs index 1dc4cc94fd..27b81c230a 100644 --- a/libraries/rawkit/src/lib.rs +++ b/libraries/rawkit/src/lib.rs @@ -172,14 +172,13 @@ impl RawImage { // Check the first two bytes to determine the format of the thumbnail. // JPEG format starts with 0xFF, 0xD8. if thumbnail_data[0..2] == [0xFF, 0xD8] { - return Ok(ThumbnailImage { + Ok(ThumbnailImage { data: thumbnail_data, format: ThumbnailFormat::Jpeg, - }); + }) } else { Err(DecoderError::UnsupportedThumbnailFormat) } - } /// Converts the [`RawImage`] to an [`Image`] with 8 bit resolution for each channel. diff --git a/libraries/rawkit/src/postprocessing/transform.rs b/libraries/rawkit/src/postprocessing/transform.rs index efc37d2ea4..3d6e4697ac 100644 --- a/libraries/rawkit/src/postprocessing/transform.rs +++ b/libraries/rawkit/src/postprocessing/transform.rs @@ -1,4 +1,4 @@ -use crate::{Image, Pixel, OrientationValue}; +use crate::{Image, OrientationValue, Pixel}; impl Image { pub fn orientation_iter(&self) -> (usize, usize, impl Iterator + use<'_>) { diff --git a/libraries/rawkit/src/tiff/types.rs b/libraries/rawkit/src/tiff/types.rs index f32a3570b0..0024b8bf22 100644 --- a/libraries/rawkit/src/tiff/types.rs +++ b/libraries/rawkit/src/tiff/types.rs @@ -1,5 +1,5 @@ use super::file::TiffRead; -use super::values::{CompressionValue, CurveLookupTable, Rational, OrientationValue}; +use super::values::{CompressionValue, CurveLookupTable, OrientationValue, Rational}; use super::{Ifd, IfdTagType, TiffError}; use std::io::{Read, Seek}; @@ -380,7 +380,7 @@ impl TagType for TypeOrientation { type Output = OrientationValue; fn read(file: &mut TiffRead) -> Result { - Ok(OrientationValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)?) + OrientationValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue) } } @@ -388,6 +388,6 @@ impl TagType for TypeCompression { type Output = CompressionValue; fn read(file: &mut TiffRead) -> Result { - Ok(CompressionValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)?) + CompressionValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue) } -} \ No newline at end of file +} diff --git a/libraries/rawkit/src/tiff/values.rs b/libraries/rawkit/src/tiff/values.rs index 0bf2d157d0..b83de14163 100644 --- a/libraries/rawkit/src/tiff/values.rs +++ b/libraries/rawkit/src/tiff/values.rs @@ -129,4 +129,4 @@ pub enum CompressionValue { JPEG_XL = 52546, Kodak_DCR_Compressed = 65000, Pentax_PEF_Compressed = 65535, -} \ No newline at end of file +}