Skip to content

Add basic thumbnail extraction to Rawkit #2659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions libraries/rawkit/src/decoder/arw1.rs
Original file line number Diff line number Diff line change
@@ -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::{OrientationValue, RawImage, SubtractBlack};
use bitstream_io::{BE, BitRead, BitReader, Endianness};
use std::io::{Read, Seek};

Expand All @@ -25,7 +25,7 @@ pub fn decode_a100<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> 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,
Expand Down
8 changes: 4 additions & 4 deletions libraries/rawkit/src/decoder/arw2.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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 crate::{OrientationValue, RawImage, SubtractBlack};
use rawkit_proc_macros::Tag;
use std::io::{Read, Seek};

Expand All @@ -26,7 +26,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> 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();
Expand All @@ -49,7 +49,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> 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,
Expand Down
7 changes: 4 additions & 3 deletions libraries/rawkit/src/decoder/uncompressed.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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::{OrientationValue, RawImage, SubtractBlack};
use rawkit_proc_macros::Tag;
use std::io::{Read, Seek};

Expand All @@ -26,7 +27,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> 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();
Expand Down Expand Up @@ -57,7 +58,7 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> 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,
Expand Down
64 changes: 51 additions & 13 deletions libraries/rawkit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,24 @@ 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::values::Transform;
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];

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<u8>,
pub format: ThumbnailFormat,
}

/// The amount of black level to be subtracted from Raw Image.
pub enum SubtractBlack {
/// Don't subtract any value.
Expand Down Expand Up @@ -48,7 +59,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,
Expand Down Expand Up @@ -97,8 +108,8 @@ pub struct Image<T> {

/// 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)]
Expand All @@ -119,15 +130,15 @@ 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::<Orientation, _>(&mut file)?;
let orientation = ifd.get_value::<Orientation, _>(&mut file)?;

let mut raw_image = if camera_model.model == "DSLR-A100" {
decoder::arw1::decode_a100(ifd, &mut file)
} else {
let sub_ifd = ifd.get_value::<SubIfd, _>(&mut file)?;
let arw_ifd = sub_ifd.get_value::<ArwIfd, _>(&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)
Expand All @@ -138,13 +149,38 @@ impl RawImage {
};

raw_image.camera_model = Some(camera_model);
raw_image.transform = transform;
raw_image.orientation = orientation;

raw_image.calculate_conversion_matrices();

Ok(raw_image)
}

/// Extracts the thumbnail image from the raw file.
pub fn extract_thumbnail<R: Read + Seek>(reader: &mut R) -> Result<ThumbnailImage, DecoderError> {
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worthwhile to do an assert on metadata::identify::identify_camera_model?

let thumbnail_offset = ifd.get_value::<ThumbnailOffset, _>(&mut file)?;
let thumbnail_length = ifd.get_value::<ThumbnailLength, _>(&mut file)?;
file.seek_from_start(thumbnail_offset)?;

let mut thumbnail_data = vec![0; thumbnail_length as usize];
file.read_exact(&mut 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] {
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.
///
/// Applies all the processing steps to finally get RGB pixel data.
Expand All @@ -156,7 +192,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,
}
}

Expand All @@ -174,7 +210,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)
Expand Down Expand Up @@ -211,7 +247,7 @@ impl RawImage {
data: image,
width: self.width,
height: self.height,
transform: self.transform,
orientation: self.orientation,
}
}
}
Expand All @@ -232,7 +268,7 @@ impl Image<u16> {

pub fn transform_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
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
Expand All @@ -246,7 +282,7 @@ impl Image<u16> {
data: image,
width,
height,
transform: Transform::Horizontal,
orientation: OrientationValue::Horizontal,
}
}
}
Expand All @@ -259,4 +295,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,
}
32 changes: 16 additions & 16 deletions libraries/rawkit/src/postprocessing/transform.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use crate::{Image, Pixel, Transform};
use crate::{Image, OrientationValue, Pixel};

impl Image<u16> {
pub fn transform_iter(&self) -> (usize, usize, impl Iterator<Item = Pixel> + use<'_>) {
let (final_width, final_height) = if self.transform.will_swap_coordinates() {
pub fn orientation_iter(&self) -> (usize, usize, impl Iterator<Item = Pixel> + 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);
Expand Down Expand Up @@ -42,16 +42,16 @@ impl Image<u16> {
}
}

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 {
Expand Down
10 changes: 5 additions & 5 deletions libraries/rawkit/src/tiff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()?;
Expand Down
16 changes: 8 additions & 8 deletions libraries/rawkit/src/tiff/tags.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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";
}

Expand Down
25 changes: 12 additions & 13 deletions libraries/rawkit/src/tiff/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::file::TiffRead;
use super::values::{CurveLookupTable, Rational, Transform};
use super::values::{CompressionValue, CurveLookupTable, OrientationValue, Rational};
use super::{Ifd, IfdTagType, TiffError};
use std::io::{Read, Seek};

Expand Down Expand Up @@ -350,6 +350,7 @@ impl<T: PrimitiveType, const N: usize> TagType for ConstArray<T, N> {
}
}

pub struct TypeCompression;
pub struct TypeString;
pub struct TypeSonyToneCurve;
pub struct TypeOrientation;
Expand All @@ -376,19 +377,17 @@ impl TagType for TypeSonyToneCurve {
}

impl TagType for TypeOrientation {
type Output = Transform;
type Output = OrientationValue;

fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
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),
})
OrientationValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)
}
}

impl TagType for TypeCompression {
type Output = CompressionValue;

fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
CompressionValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)
}
}
Loading
Loading