Skip to content

Commit c21ccf5

Browse files
authored
Add basic thumbnail extraction to Rawkit (#2659)
* Add `CompressionValue` * Rename `Transform` to `OrientationValue` * Add basic thumbnail extraction * Add `ThumbnailFormat` * fmt + clippy fixes
1 parent d441a02 commit c21ccf5

File tree

9 files changed

+171
-80
lines changed

9 files changed

+171
-80
lines changed

libraries/rawkit/src/decoder/arw1.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::tiff::Ifd;
22
use crate::tiff::file::TiffRead;
33
use crate::tiff::tags::SonyDataOffset;
4-
use crate::{RawImage, SubtractBlack, Transform};
4+
use crate::{OrientationValue, RawImage, SubtractBlack};
55
use bitstream_io::{BE, BitRead, BitReader, Endianness};
66
use std::io::{Read, Seek};
77

@@ -25,7 +25,7 @@ pub fn decode_a100<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage
2525
#[allow(unreachable_code)]
2626
maximum: (1 << 12) - 1,
2727
black: SubtractBlack::None,
28-
transform: Transform::Horizontal,
28+
orientation: OrientationValue::Horizontal,
2929
camera_model: None,
3030
camera_white_balance: None,
3131
white_balance: None,

libraries/rawkit/src/decoder/arw2.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::tiff::file::{Endian, TiffRead};
22
use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels};
3-
use crate::tiff::values::CurveLookupTable;
3+
use crate::tiff::values::{CompressionValue, CurveLookupTable};
44
use crate::tiff::{Ifd, TiffError};
5-
use crate::{RawImage, SubtractBlack, Transform};
5+
use crate::{OrientationValue, RawImage, SubtractBlack};
66
use rawkit_proc_macros::Tag;
77
use std::io::{Read, Seek};
88

@@ -26,7 +26,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
2626

2727
assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len());
2828
assert!(ifd.strip_offsets.len() == 1);
29-
assert!(ifd.compression == 32767);
29+
assert!(ifd.compression == CompressionValue::Sony_ARW_Compressed);
3030

3131
let image_width: usize = ifd.image_width.try_into().unwrap();
3232
let image_height: usize = ifd.image_height.try_into().unwrap();
@@ -49,7 +49,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
4949
cfa_pattern: ifd.cfa_pattern.try_into().unwrap(),
5050
maximum: (1 << 14) - 1,
5151
black: SubtractBlack::CfaGrid([512, 512, 512, 512]), // TODO: Find the correct way to do this
52-
transform: Transform::Horizontal,
52+
orientation: OrientationValue::Horizontal,
5353
camera_model: None,
5454
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
5555
white_balance: None,

libraries/rawkit/src/decoder/uncompressed.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::tiff::file::TiffRead;
22
use crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels};
3+
use crate::tiff::values::CompressionValue;
34
use crate::tiff::{Ifd, TiffError};
4-
use crate::{RawImage, SubtractBlack, Transform};
5+
use crate::{OrientationValue, RawImage, SubtractBlack};
56
use rawkit_proc_macros::Tag;
67
use std::io::{Read, Seek};
78

@@ -26,7 +27,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
2627

2728
assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len());
2829
assert!(ifd.strip_offsets.len() == 1);
29-
assert!(ifd.compression == 1); // 1 is the value for uncompressed format
30+
assert!(ifd.compression == CompressionValue::Uncompressed);
3031

3132
let image_width: usize = ifd.image_width.try_into().unwrap();
3233
let image_height: usize = ifd.image_height.try_into().unwrap();
@@ -57,7 +58,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
5758
cfa_pattern: ifd.cfa_pattern.try_into().unwrap(),
5859
maximum: if bits_per_sample == 16 { u16::MAX } else { (1 << bits_per_sample) - 1 },
5960
black: SubtractBlack::CfaGrid(ifd.black_level),
60-
transform: Transform::Horizontal,
61+
orientation: OrientationValue::Horizontal,
6162
camera_model: None,
6263
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
6364
white_balance: None,

libraries/rawkit/src/lib.rs

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,24 @@ use rawkit_proc_macros::Tag;
1212
use std::io::{Read, Seek};
1313
use thiserror::Error;
1414
use tiff::file::TiffRead;
15-
use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag};
16-
use tiff::values::Transform;
15+
use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag, ThumbnailLength, ThumbnailOffset};
16+
use tiff::values::{CompressionValue, OrientationValue};
1717
use tiff::{Ifd, TiffError};
1818

1919
pub(crate) const CHANNELS_IN_RGB: usize = 3;
2020
pub(crate) type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB];
2121

22+
pub enum ThumbnailFormat {
23+
Jpeg,
24+
Unsupported,
25+
}
26+
27+
/// A thumbnail image extracted from the raw file. This is usually a JPEG image.
28+
pub struct ThumbnailImage {
29+
pub data: Vec<u8>,
30+
pub format: ThumbnailFormat,
31+
}
32+
2233
/// The amount of black level to be subtracted from Raw Image.
2334
pub enum SubtractBlack {
2435
/// Don't subtract any value.
@@ -48,7 +59,7 @@ pub struct RawImage {
4859
pub cfa_pattern: [u8; 4],
4960

5061
/// Transformation to be applied to negate the orientation of camera.
51-
pub transform: Transform,
62+
pub orientation: OrientationValue,
5263

5364
/// The maximum possible value of pixel that the camera sensor could give.
5465
pub maximum: u16,
@@ -97,8 +108,8 @@ pub struct Image<T> {
97108

98109
/// The transformation required to orient the image correctly.
99110
///
100-
/// This will be [`Transform::Horizontal`] after the transform step is applied.
101-
pub transform: Transform,
111+
/// This will be [`OrientationValue::Horizontal`] after the orientation step is applied.
112+
pub orientation: OrientationValue,
102113
}
103114

104115
#[allow(dead_code)]
@@ -119,15 +130,15 @@ impl RawImage {
119130
let ifd = Ifd::new_first_ifd(&mut file)?;
120131

121132
let camera_model = metadata::identify::identify_camera_model(&ifd, &mut file).unwrap();
122-
let transform = ifd.get_value::<Orientation, _>(&mut file)?;
133+
let orientation = ifd.get_value::<Orientation, _>(&mut file)?;
123134

124135
let mut raw_image = if camera_model.model == "DSLR-A100" {
125136
decoder::arw1::decode_a100(ifd, &mut file)
126137
} else {
127138
let sub_ifd = ifd.get_value::<SubIfd, _>(&mut file)?;
128139
let arw_ifd = sub_ifd.get_value::<ArwIfd, _>(&mut file)?;
129140

130-
if arw_ifd.compression == 1 {
141+
if arw_ifd.compression == CompressionValue::Uncompressed {
131142
decoder::uncompressed::decode(sub_ifd, &mut file)
132143
} else if arw_ifd.strip_byte_counts[0] == arw_ifd.image_width * arw_ifd.image_height {
133144
decoder::arw2::decode(sub_ifd, &mut file)
@@ -138,13 +149,38 @@ impl RawImage {
138149
};
139150

140151
raw_image.camera_model = Some(camera_model);
141-
raw_image.transform = transform;
152+
raw_image.orientation = orientation;
142153

143154
raw_image.calculate_conversion_matrices();
144155

145156
Ok(raw_image)
146157
}
147158

159+
/// Extracts the thumbnail image from the raw file.
160+
pub fn extract_thumbnail<R: Read + Seek>(reader: &mut R) -> Result<ThumbnailImage, DecoderError> {
161+
let mut file = TiffRead::new(reader)?;
162+
let ifd = Ifd::new_first_ifd(&mut file)?;
163+
164+
// TODO: ARW files Store the thumbnail offset and length in the first IFD. Add support for other file types in the future.
165+
let thumbnail_offset = ifd.get_value::<ThumbnailOffset, _>(&mut file)?;
166+
let thumbnail_length = ifd.get_value::<ThumbnailLength, _>(&mut file)?;
167+
file.seek_from_start(thumbnail_offset)?;
168+
169+
let mut thumbnail_data = vec![0; thumbnail_length as usize];
170+
file.read_exact(&mut thumbnail_data)?;
171+
172+
// Check the first two bytes to determine the format of the thumbnail.
173+
// JPEG format starts with 0xFF, 0xD8.
174+
if thumbnail_data[0..2] == [0xFF, 0xD8] {
175+
Ok(ThumbnailImage {
176+
data: thumbnail_data,
177+
format: ThumbnailFormat::Jpeg,
178+
})
179+
} else {
180+
Err(DecoderError::UnsupportedThumbnailFormat)
181+
}
182+
}
183+
148184
/// Converts the [`RawImage`] to an [`Image`] with 8 bit resolution for each channel.
149185
///
150186
/// Applies all the processing steps to finally get RGB pixel data.
@@ -156,7 +192,7 @@ impl RawImage {
156192
data: image.data.iter().map(|x| (x >> 8) as u8).collect(),
157193
width: image.width,
158194
height: image.height,
159-
transform: image.transform,
195+
orientation: image.orientation,
160196
}
161197
}
162198

@@ -174,7 +210,7 @@ impl RawImage {
174210
let image = raw_image.demosaic_and_apply((convert_to_rgb, &mut record_histogram));
175211

176212
let gamma_correction = image.gamma_correction_fn(&record_histogram.histogram);
177-
if image.transform == Transform::Horizontal {
213+
if image.orientation == OrientationValue::Horizontal {
178214
image.apply(gamma_correction)
179215
} else {
180216
image.transform_and_apply(gamma_correction)
@@ -211,7 +247,7 @@ impl RawImage {
211247
data: image,
212248
width: self.width,
213249
height: self.height,
214-
transform: self.transform,
250+
orientation: self.orientation,
215251
}
216252
}
217253
}
@@ -232,7 +268,7 @@ impl Image<u16> {
232268

233269
pub fn transform_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
234270
let mut image = vec![0; self.width * self.height * 3];
235-
let (width, height, iter) = self.transform_iter();
271+
let (width, height, iter) = self.orientation_iter();
236272
for Pixel { values, row, column } in iter.map(|mut pixel| {
237273
pixel.values = transform.apply(pixel);
238274
pixel
@@ -246,7 +282,7 @@ impl Image<u16> {
246282
data: image,
247283
width,
248284
height,
249-
transform: Transform::Horizontal,
285+
orientation: OrientationValue::Horizontal,
250286
}
251287
}
252288
}
@@ -259,4 +295,6 @@ pub enum DecoderError {
259295
ConversionError(#[from] std::num::TryFromIntError),
260296
#[error("An IO Error ocurred")]
261297
IoError(#[from] std::io::Error),
298+
#[error("The thumbnail format is unsupported")]
299+
UnsupportedThumbnailFormat,
262300
}

libraries/rawkit/src/postprocessing/transform.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use crate::{Image, Pixel, Transform};
1+
use crate::{Image, OrientationValue, Pixel};
22

33
impl Image<u16> {
4-
pub fn transform_iter(&self) -> (usize, usize, impl Iterator<Item = Pixel> + use<'_>) {
5-
let (final_width, final_height) = if self.transform.will_swap_coordinates() {
4+
pub fn orientation_iter(&self) -> (usize, usize, impl Iterator<Item = Pixel> + use<'_>) {
5+
let (final_width, final_height) = if self.orientation.will_swap_coordinates() {
66
(self.height, self.width)
77
} else {
88
(self.width, self.height)
99
};
1010

11-
let index_0_0 = inverse_transform_index(self.transform, 0, 0, self.width, self.height);
12-
let index_0_1 = inverse_transform_index(self.transform, 0, 1, self.width, self.height);
13-
let index_1_0 = inverse_transform_index(self.transform, 1, 0, self.width, self.height);
11+
let index_0_0 = inverse_orientation_index(self.orientation, 0, 0, self.width, self.height);
12+
let index_0_1 = inverse_orientation_index(self.orientation, 0, 1, self.width, self.height);
13+
let index_1_0 = inverse_orientation_index(self.orientation, 1, 0, self.width, self.height);
1414

1515
let column_step = (index_0_1.0 - index_0_0.0, index_0_1.1 - index_0_0.1);
1616
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<u16> {
4242
}
4343
}
4444

45-
pub fn inverse_transform_index(transform: Transform, mut row: usize, mut column: usize, width: usize, height: usize) -> (i64, i64) {
46-
let value = match transform {
47-
Transform::Horizontal => 0,
48-
Transform::MirrorHorizontal => 1,
49-
Transform::Rotate180 => 3,
50-
Transform::MirrorVertical => 2,
51-
Transform::MirrorHorizontalRotate270 => 4,
52-
Transform::Rotate90 => 6,
53-
Transform::MirrorHorizontalRotate90 => 7,
54-
Transform::Rotate270 => 5,
45+
pub fn inverse_orientation_index(orientation: OrientationValue, mut row: usize, mut column: usize, width: usize, height: usize) -> (i64, i64) {
46+
let value = match orientation {
47+
OrientationValue::Horizontal => 0,
48+
OrientationValue::MirrorHorizontal => 1,
49+
OrientationValue::Rotate180 => 3,
50+
OrientationValue::MirrorVertical => 2,
51+
OrientationValue::MirrorHorizontalRotate270 => 4,
52+
OrientationValue::Rotate90 => 6,
53+
OrientationValue::MirrorHorizontalRotate90 => 7,
54+
OrientationValue::Rotate270 => 5,
5555
};
5656

5757
if value & 4 != 0 {

libraries/rawkit/src/tiff/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ pub enum TagId {
2626
RowsPerStrip = 0x116,
2727
StripByteCounts = 0x117,
2828
SubIfd = 0x14a,
29-
JpegOffset = 0x201,
30-
JpegLength = 0x202,
29+
ThumbnailOffset = 0x201,
30+
ThumbnailLength = 0x202,
3131
SonyToneCurve = 0x7010,
3232
BlackLevel = 0x7310,
3333
WhiteBalanceRggbLevels = 0x7313,
@@ -88,10 +88,10 @@ impl Ifd {
8888
}
8989

9090
file.seek_from_start(offset)?;
91-
let num = file.read_u16()?;
91+
let num_entries = file.read_u16()?;
9292

93-
let mut ifd_entries = Vec::with_capacity(num.into());
94-
for _ in 0..num {
93+
let mut ifd_entries = Vec::with_capacity(num_entries.into());
94+
for _ in 0..num_entries {
9595
let tag = file.read_u16()?.into();
9696
let the_type = file.read_u16()?.into();
9797
let count = file.read_u32()?;

libraries/rawkit/src/tiff/tags.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeOrientation, TypeSRational, TypeSShort, TypeShort, TypeSonyToneCurve, TypeString};
1+
use super::types::{Array, ConstArray, TagType, TypeByte, TypeCompression, TypeIfd, TypeLong, TypeNumber, TypeOrientation, TypeSRational, TypeSShort, TypeShort, TypeSonyToneCurve, TypeString};
22
use super::{Ifd, TagId, TiffError, TiffRead};
33
use std::io::{Read, Seek};
44

@@ -22,8 +22,8 @@ pub struct SamplesPerPixel;
2222
pub struct RowsPerStrip;
2323
pub struct StripByteCounts;
2424
pub struct SubIfd;
25-
pub struct JpegOffset;
26-
pub struct JpegLength;
25+
pub struct ThumbnailOffset;
26+
pub struct ThumbnailLength;
2727
pub struct SonyDataOffset;
2828
pub struct SonyToneCurve;
2929
pub struct BlackLevel;
@@ -55,7 +55,7 @@ impl SimpleTag for BitsPerSample {
5555
}
5656

5757
impl SimpleTag for Compression {
58-
type Type = TypeShort;
58+
type Type = TypeCompression;
5959

6060
const ID: TagId = TagId::Compression;
6161
const NAME: &'static str = "Compression";
@@ -124,17 +124,17 @@ impl SimpleTag for SubIfd {
124124
const NAME: &'static str = "SubIFD";
125125
}
126126

127-
impl SimpleTag for JpegOffset {
127+
impl SimpleTag for ThumbnailOffset {
128128
type Type = TypeLong;
129129

130-
const ID: TagId = TagId::JpegOffset;
130+
const ID: TagId = TagId::ThumbnailOffset;
131131
const NAME: &'static str = "Jpeg Offset";
132132
}
133133

134-
impl SimpleTag for JpegLength {
134+
impl SimpleTag for ThumbnailLength {
135135
type Type = TypeLong;
136136

137-
const ID: TagId = TagId::JpegLength;
137+
const ID: TagId = TagId::ThumbnailLength;
138138
const NAME: &'static str = "Jpeg Length";
139139
}
140140

libraries/rawkit/src/tiff/types.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::file::TiffRead;
2-
use super::values::{CurveLookupTable, Rational, Transform};
2+
use super::values::{CompressionValue, CurveLookupTable, OrientationValue, Rational};
33
use super::{Ifd, IfdTagType, TiffError};
44
use std::io::{Read, Seek};
55

@@ -350,6 +350,7 @@ impl<T: PrimitiveType, const N: usize> TagType for ConstArray<T, N> {
350350
}
351351
}
352352

353+
pub struct TypeCompression;
353354
pub struct TypeString;
354355
pub struct TypeSonyToneCurve;
355356
pub struct TypeOrientation;
@@ -376,19 +377,17 @@ impl TagType for TypeSonyToneCurve {
376377
}
377378

378379
impl TagType for TypeOrientation {
379-
type Output = Transform;
380+
type Output = OrientationValue;
380381

381382
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
382-
Ok(match TypeShort::read(file)? {
383-
1 => Transform::Horizontal,
384-
2 => Transform::MirrorHorizontal,
385-
3 => Transform::Rotate180,
386-
4 => Transform::MirrorVertical,
387-
5 => Transform::MirrorHorizontalRotate270,
388-
6 => Transform::Rotate90,
389-
7 => Transform::MirrorHorizontalRotate90,
390-
8 => Transform::Rotate270,
391-
_ => return Err(TiffError::InvalidValue),
392-
})
383+
OrientationValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)
384+
}
385+
}
386+
387+
impl TagType for TypeCompression {
388+
type Output = CompressionValue;
389+
390+
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
391+
CompressionValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)
393392
}
394393
}

0 commit comments

Comments
 (0)