Rename Raw-rs to Rawkit (#2088)

* Rename within files

* Rename in CI

* Rename the folder and file names

* Rename raw_rs to rawkit

* Add example to README

* Add initial documentation

* Small API changes and extra documentation

* Bump versions and stuff

* Readme improvements

* Merge proc-macro crates into one

* Add README to rawkit-proc-macros

* Remove keywords and categories

* Add licenses to rawkit-proc-macros

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Elbert Ronnie
2024-11-03 13:40:39 +05:30
committed by GitHub
parent 8d3da83606
commit 8fdecaa487
89 changed files with 664 additions and 356 deletions

View File

@@ -0,0 +1,109 @@
use crate::tiff::file::TiffRead;
use crate::tiff::tags::SonyDataOffset;
use crate::tiff::Ifd;
use crate::{RawImage, SubtractBlack, Transform};
use bitstream_io::{BitRead, BitReader, Endianness, BE};
use std::io::{Read, Seek};
pub fn decode_a100<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
let data_offset = ifd.get_value::<SonyDataOffset, _>(file).unwrap();
let image_width = 3881;
let image_height = 2608;
file.seek_from_start(data_offset).unwrap();
let mut image = sony_arw_load_raw(image_width, image_height, &mut BitReader::<_, BE>::new(file)).unwrap();
let len = image.len();
image[len - image_width..].fill(0);
RawImage {
data: image,
width: image_width,
height: image_height,
cfa_pattern: todo!(),
#[allow(unreachable_code)]
maximum: (1 << 12) - 1,
black: SubtractBlack::None,
transform: Transform::Horizontal,
camera_model: None,
camera_white_balance: None,
white_balance: None,
camera_to_rgb: None,
}
}
fn read_and_huffman_decode_file<R: Read + Seek, E: Endianness>(huff: &[u16], file: &mut BitReader<R, E>) -> u32 {
let number_of_bits = huff[0].into();
let huffman_table = &huff[1..];
// `number_of_bits` will be no more than 32, so the result is put into a u32
let bits: u32 = file.read(number_of_bits).unwrap();
let bits = bits as usize;
let bits_to_seek_from = huffman_table[bits].to_le_bytes()[1] as i64 - number_of_bits as i64;
file.seek_bits(std::io::SeekFrom::Current(bits_to_seek_from)).unwrap();
huffman_table[bits].to_le_bytes()[0].into()
}
fn read_n_bits_from_file<R: Read + Seek, E: Endianness>(number_of_bits: u32, file: &mut BitReader<R, E>) -> u32 {
// `number_of_bits` will be no more than 32, so the result is put into a u32
file.read(number_of_bits).unwrap()
}
/// ljpeg is a lossless variant of JPEG which gets used for decoding the embedded (thumbnail) preview images in raw files
fn ljpeg_diff<R: Read + Seek, E: Endianness>(huff: &[u16], file: &mut BitReader<R, E>, dng_version: Option<u32>) -> i32 {
let length = read_and_huffman_decode_file(huff, file);
if length == 16 && dng_version.map(|x| x >= 0x1010000).unwrap_or(true) {
return -32768;
}
let diff = read_n_bits_from_file(length, file) as i32;
if length == 0 || (diff & (1 << (length - 1))) == 0 {
diff - (1 << length) - 1
} else {
diff
}
}
fn sony_arw_load_raw<R: Read + Seek>(width: usize, height: usize, file: &mut BitReader<R, BE>) -> Option<Vec<u16>> {
const TABLE: [u16; 18] = [
0x0f11, 0x0f10, 0x0e0f, 0x0d0e, 0x0c0d, 0x0b0c, 0x0a0b, 0x090a, 0x0809, 0x0708, 0x0607, 0x0506, 0x0405, 0x0304, 0x0303, 0x0300, 0x0202, 0x0201,
];
let mut huffman_table = [0_u16; 32770];
// The first element is the number of bits to read
huffman_table[0] = 15;
let mut n = 0;
for x in TABLE {
let first_byte = x >> 8;
let repeats = 0x8000 >> first_byte;
for _ in 0_u16..repeats {
n += 1;
huffman_table[n] = x;
}
}
let mut sum = 0;
let mut image = vec![0_u16; width * height];
for column in (0..width).rev() {
for row in (0..height).step_by(2).chain((1..height).step_by(2)) {
sum += ljpeg_diff(&huffman_table, file, None);
if (sum >> 12) != 0 {
return None;
}
if row < height {
image[row * width + column] = sum as u16;
}
}
}
Some(image)
}

View File

@@ -0,0 +1,127 @@
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::{Ifd, TiffError};
use crate::{RawImage, SubtractBlack, Transform};
use rawkit_proc_macros::Tag;
use std::io::{Read, Seek};
#[allow(dead_code)]
#[derive(Tag)]
struct Arw2Ifd {
image_width: ImageWidth,
image_height: ImageLength,
bits_per_sample: BitsPerSample,
compression: Compression,
cfa_pattern: CfaPattern,
cfa_pattern_dim: CfaPatternDim,
strip_offsets: StripOffsets,
strip_byte_counts: StripByteCounts,
sony_tone_curve: SonyToneCurve,
white_balance_levels: Option<WhiteBalanceRggbLevels>,
}
pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
let ifd = ifd.get_value::<Arw2Ifd, _>(file).unwrap();
assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len());
assert!(ifd.strip_offsets.len() == 1);
assert!(ifd.compression == 32767);
let image_width: usize = ifd.image_width.try_into().unwrap();
let image_height: usize = ifd.image_height.try_into().unwrap();
let bits_per_sample: usize = ifd.bits_per_sample.into();
assert!(bits_per_sample == 12);
let [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim;
assert!(cfa_pattern_width == 2 && cfa_pattern_height == 2);
file.seek_from_start(ifd.strip_offsets[0]).unwrap();
let mut image = sony_arw2_load_raw(image_width, image_height, ifd.sony_tone_curve, file).unwrap();
// Converting the bps from 12 to 14 so that ARW 2.3.1 and 2.3.5 have the same 14 bps.
image.iter_mut().for_each(|x| *x <<= 2);
RawImage {
data: image,
width: image_width,
height: image_height,
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,
camera_model: None,
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
white_balance: None,
camera_to_rgb: None,
}
}
fn as_u32(buffer: &[u8], endian: Endian) -> Option<u32> {
Some(match endian {
Endian::Little => u32::from_le_bytes(buffer.try_into().ok()?),
Endian::Big => u32::from_be_bytes(buffer.try_into().ok()?),
})
}
fn as_u16(buffer: &[u8], endian: Endian) -> Option<u16> {
Some(match endian {
Endian::Little => u16::from_le_bytes(buffer.try_into().ok()?),
Endian::Big => u16::from_be_bytes(buffer.try_into().ok()?),
})
}
fn sony_arw2_load_raw<R: Read + Seek>(width: usize, height: usize, curve: CurveLookupTable, file: &mut TiffRead<R>) -> Option<Vec<u16>> {
let mut image = vec![0_u16; height * width];
let mut data = vec![0_u8; width + 1];
for row in 0..height {
file.read_exact(&mut data[0..width]).unwrap();
let mut column = 0;
let mut data_index = 0;
while column < width - 30 {
let data_value = as_u32(&data[data_index..][..4], file.endian()).unwrap();
let max = (0x7ff & data_value) as u16;
let min = (0x7ff & data_value >> 11) as u16;
let index_to_set_max = 0x0f & data_value >> 22;
let index_to_set_min = 0x0f & data_value >> 26;
let max_minus_min = max as i32 - min as i32;
let shift_by_bits = (0..4).find(|&shift| (0x80 << shift) > max_minus_min).unwrap_or(4);
let mut pixels = [0_u16; 16];
let mut bit = 30;
for (i, pixel) in pixels.iter_mut().enumerate() {
*pixel = match () {
_ if i as u32 == index_to_set_max => max,
_ if i as u32 == index_to_set_min => min,
_ => {
let result = as_u16(&data[(data_index + (bit >> 3))..][..2], file.endian()).unwrap();
let result = ((result >> (bit & 7)) & 0x07f) << shift_by_bits;
bit += 7;
(result + min).min(0x7ff)
}
};
}
for value in pixels {
image[row * width + column] = curve.get((value << 1).into()) >> 2;
// Skip between interlaced columns
column += 2;
}
// Switch to the opposite interlaced columns
column -= if column & 1 == 0 { 31 } else { 1 };
data_index += 16;
}
}
Some(image)
}

View File

@@ -0,0 +1,3 @@
pub mod arw1;
pub mod arw2;
pub mod uncompressed;

View File

@@ -0,0 +1,67 @@
use crate::tiff::file::TiffRead;
use crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels};
use crate::tiff::{Ifd, TiffError};
use crate::{RawImage, SubtractBlack, Transform};
use rawkit_proc_macros::Tag;
use std::io::{Read, Seek};
#[allow(dead_code)]
#[derive(Tag)]
struct ArwUncompressedIfd {
image_width: ImageWidth,
image_height: ImageLength,
rows_per_strip: RowsPerStrip,
bits_per_sample: BitsPerSample,
compression: Compression,
black_level: BlackLevel,
cfa_pattern: CfaPattern,
cfa_pattern_dim: CfaPatternDim,
strip_offsets: StripOffsets,
strip_byte_counts: StripByteCounts,
white_balance_levels: Option<WhiteBalanceRggbLevels>,
}
pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
let ifd = ifd.get_value::<ArwUncompressedIfd, _>(file).unwrap();
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
let image_width: usize = ifd.image_width.try_into().unwrap();
let image_height: usize = ifd.image_height.try_into().unwrap();
let rows_per_strip: usize = ifd.rows_per_strip.try_into().unwrap();
let bits_per_sample: usize = ifd.bits_per_sample.into();
let [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim;
assert!(cfa_pattern_width == 2 && cfa_pattern_height == 2);
let mut image: Vec<u16> = Vec::with_capacity(image_height * image_width);
for i in 0..ifd.strip_offsets.len() {
file.seek_from_start(ifd.strip_offsets[i]).unwrap();
let last = i == ifd.strip_offsets.len();
let rows = if last { image_height % rows_per_strip } else { rows_per_strip };
for _ in 0..rows {
for _ in 0..image_width {
image.push(file.read_u16().unwrap());
}
}
}
RawImage {
data: image,
width: image_width,
height: image_height,
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,
camera_model: None,
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
white_balance: None,
camera_to_rgb: None,
}
}

View File

@@ -0,0 +1,81 @@
use crate::{Pixel, RawImage};
fn average(data: &[u16], indexes: impl Iterator<Item = i64>) -> u16 {
let mut sum = 0;
let mut count = 0;
for index in indexes {
if index >= 0 && (index as usize) < data.len() {
sum += data[index as usize] as u32;
count += 1;
}
}
(sum / count) as u16
}
impl RawImage {
pub fn linear_demosaic_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {
match self.cfa_pattern {
[0, 1, 1, 2] => self.linear_demosaic_rggb_iter(),
_ => todo!(),
}
}
fn linear_demosaic_rggb_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {
let width = self.width as i64;
let height = self.height as i64;
(0..height).flat_map(move |row| {
let row_by_width = row * width;
(0..width).map(move |column| {
let pixel_index = row_by_width + column;
let vertical_indexes = [pixel_index + width, pixel_index - width];
let horizontal_indexes = [pixel_index + 1, pixel_index - 1];
let cross_indexes = [pixel_index + width, pixel_index - width, pixel_index + 1, pixel_index - 1];
let diagonal_indexes = [pixel_index + width + 1, pixel_index - width + 1, pixel_index + width - 1, pixel_index - width - 1];
let pixel_index = pixel_index as usize;
match (row % 2 == 0, column % 2 == 0) {
(true, true) => Pixel {
values: [
self.data[pixel_index],
average(&self.data, cross_indexes.into_iter()),
average(&self.data, diagonal_indexes.into_iter()),
],
row: row as usize,
column: column as usize,
},
(true, false) => Pixel {
values: [
average(&self.data, horizontal_indexes.into_iter()),
self.data[pixel_index],
average(&self.data, vertical_indexes.into_iter()),
],
row: row as usize,
column: column as usize,
},
(false, true) => Pixel {
values: [
average(&self.data, vertical_indexes.into_iter()),
self.data[pixel_index],
average(&self.data, horizontal_indexes.into_iter()),
],
row: row as usize,
column: column as usize,
},
(false, false) => Pixel {
values: [
average(&self.data, diagonal_indexes.into_iter()),
average(&self.data, cross_indexes.into_iter()),
self.data[pixel_index],
],
row: row as usize,
column: column as usize,
},
}
})
})
}
}

View File

@@ -0,0 +1 @@
pub mod linear_demosaicing;

264
libraries/rawkit/src/lib.rs Normal file
View File

@@ -0,0 +1,264 @@
pub mod decoder;
pub mod demosaicing;
pub mod metadata;
pub mod postprocessing;
pub mod preprocessing;
pub mod processing;
pub mod tiff;
use crate::metadata::identify::CameraModel;
use processing::{Pixel, PixelTransform, RawPixel, RawPixelTransform};
use rawkit_proc_macros::Tag;
use tiff::file::TiffRead;
use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag};
use tiff::values::Transform;
use tiff::{Ifd, TiffError};
use std::io::{Read, Seek};
use thiserror::Error;
pub(crate) const CHANNELS_IN_RGB: usize = 3;
pub(crate) type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB];
/// The amount of black level to be subtracted from Raw Image.
pub enum SubtractBlack {
/// Don't subtract any value.
None,
/// Subtract a singular value for all pixels in Bayer CFA Grid.
Value(u16),
/// Subtract the appropriate value for pixels in Bayer CFA Grid.
CfaGrid([u16; 4]),
}
/// Represents a Raw Image along with its metadata.
pub struct RawImage {
/// Raw pixel data stored in linear fashion.
pub data: Vec<u16>,
/// Width of the raw image.
pub width: usize,
/// Height of the raw image.
pub height: usize,
/// Bayer CFA pattern used to arrange pixels in [`RawImage::data`].
///
/// It encodes Red, Blue and Green as 0, 1, and 2 respectively.
pub cfa_pattern: [u8; 4],
/// Transformation to be applied to negate the orientation of camera.
pub transform: Transform,
/// The maximum possible value of pixel that the camera sensor could give.
pub maximum: u16,
/// The minimum possible value of pixel that the camera sensor could give.
///
/// Used to subtract the black level from the raw image.
pub black: SubtractBlack,
/// Information regarding the company and model of the camera.
pub camera_model: Option<CameraModel>,
/// White balance specified in the metadata of the raw file.
///
/// It represents the 4 values of CFA Grid which follows the same pattern as [`RawImage::cfa_pattern`].
pub camera_white_balance: Option<[f64; 4]>,
/// White balance of the raw image.
///
/// It is the same as [`RawImage::camera_white_balance`] if the raw file contains the metadata.
/// Otherwise it falls back to calculating the white balance from the color space conversion matrix.
///
/// It represents the 4 values of CFA Grid which follows the same pattern as [`RawImage::cfa_pattern`].
pub white_balance: Option<[f64; 4]>,
/// Color space conversion matrix to convert from camera's color space to sRGB.
pub camera_to_rgb: Option<[[f64; 3]; 3]>,
}
/// Represents the final RGB Image.
pub struct Image<T> {
/// Pixel data stored in a linear fashion.
pub data: Vec<T>,
/// Width of the image.
pub width: usize,
/// Height of the image.
pub height: usize,
/// The number of color channels in the image.
///
/// We can assume this will be 3 for all non-obscure, modern cameras.
/// See <https://github.com/GraphiteEditor/Graphite/pull/1923#discussion_r1725070342> for more information.
pub channels: u8,
/// The transformation required to orient the image correctly.
///
/// This will be [`Transform::Horizontal`] after the transform step is applied.
pub transform: Transform,
}
#[allow(dead_code)]
#[derive(Tag)]
struct ArwIfd {
image_width: ImageWidth,
image_height: ImageLength,
compression: Compression,
strip_byte_counts: StripByteCounts,
}
impl RawImage {
/// Create a [`RawImage`] from an input stream.
///
/// Decodes the contents of `reader` and extracts raw pixel data and metadata.
pub fn decode<R: Read + Seek>(reader: &mut R) -> Result<RawImage, DecoderError> {
let mut file = TiffRead::new(reader)?;
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 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 {
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)
} else {
// TODO: implement for arw 1.
todo!()
}
};
raw_image.camera_model = Some(camera_model);
raw_image.transform = transform;
raw_image.calculate_conversion_matrices();
Ok(raw_image)
}
/// Converts the [`RawImage`] to an [`Image`] with 8 bit resolution for each channel.
///
/// Applies all the processing steps to finally get RGB pixel data.
pub fn process_8bit(self) -> Image<u8> {
let image = self.process_16bit();
Image {
channels: image.channels,
data: image.data.iter().map(|x| (x >> 8) as u8).collect(),
width: image.width,
height: image.height,
transform: image.transform,
}
}
/// Converts the [`RawImage`] to an [`Image`] with 16 bit resolution for each channel.
///
/// Applies all the processing steps to finally get RGB pixel data.
pub fn process_16bit(self) -> Image<u16> {
let subtract_black = self.subtract_black_fn();
let scale_white_balance = self.scale_white_balance_fn();
let scale_to_16bit = self.scale_to_16bit_fn();
let raw_image = self.apply((subtract_black, scale_white_balance, scale_to_16bit));
let convert_to_rgb = raw_image.convert_to_rgb_fn();
let mut record_histogram = raw_image.record_histogram_fn();
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 {
image.apply(gamma_correction)
} else {
image.transform_and_apply(gamma_correction)
}
}
}
impl RawImage {
pub fn apply(mut self, mut transform: impl RawPixelTransform) -> RawImage {
for (index, value) in self.data.iter_mut().enumerate() {
let pixel = RawPixel {
value: *value,
row: index / self.width,
column: index % self.width,
};
*value = transform.apply(pixel);
}
self
}
pub fn demosaic_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
let mut image = vec![0; self.width * self.height * 3];
for Pixel { values, row, column } in self.linear_demosaic_iter().map(|mut pixel| {
pixel.values = transform.apply(pixel);
pixel
}) {
let pixel_index = row * self.width + column;
image[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);
}
Image {
channels: 3,
data: image,
width: self.width,
height: self.height,
transform: self.transform,
}
}
}
impl Image<u16> {
pub fn apply(mut self, mut transform: impl PixelTransform) -> Image<u16> {
for (index, values) in self.data.chunks_exact_mut(3).enumerate() {
let pixel = Pixel {
values: values.try_into().unwrap(),
row: index / self.width,
column: index % self.width,
};
values.copy_from_slice(&transform.apply(pixel));
}
self
}
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();
for Pixel { values, row, column } in iter.map(|mut pixel| {
pixel.values = transform.apply(pixel);
pixel
}) {
let pixel_index = row * width + column;
image[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);
}
Image {
channels: 3,
data: image,
width,
height,
transform: Transform::Horizontal,
}
}
}
#[derive(Error, Debug)]
pub enum DecoderError {
#[error("An error occurred when trying to parse the TIFF format")]
TiffError(#[from] TiffError),
#[error("An error occurred when converting integer from one type to another")]
ConversionError(#[from] std::num::TryFromIntError),
#[error("An IO Error ocurred")]
IoError(#[from] std::io::Error),
}

View File

@@ -0,0 +1,118 @@
use crate::RawImage;
use rawkit_proc_macros::build_camera_data;
pub struct CameraData {
pub black: u16,
pub maximum: u16,
pub xyz_to_camera: [i16; 9],
}
impl CameraData {
const DEFAULT: CameraData = CameraData {
black: 0,
maximum: 0,
xyz_to_camera: [0; 9],
};
}
const CAMERA_DATA: [(&str, CameraData); 40] = build_camera_data!();
const RGB_TO_XYZ: [[f64; 3]; 3] = [
// Matrix:
[0.412453, 0.357580, 0.180423],
[0.212671, 0.715160, 0.072169],
[0.019334, 0.119193, 0.950227],
];
impl RawImage {
pub fn calculate_conversion_matrices(&mut self) {
let Some(ref camera_model) = self.camera_model else { return };
let camera_name_needle = camera_model.make.to_owned() + " " + &camera_model.model;
let xyz_to_camera = CAMERA_DATA
.iter()
.find(|(camera_name_haystack, _)| camera_name_needle == *camera_name_haystack)
.map(|(_, data)| data.xyz_to_camera.map(|x| (x as f64) / 10_000.));
let Some(xyz_to_camera) = xyz_to_camera else { return };
let mut rgb_to_camera = [[0.; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
rgb_to_camera[i][j] += RGB_TO_XYZ[k][j] * xyz_to_camera[i * 3 + k];
}
}
}
let white_balance_multiplier = rgb_to_camera.map(|x| 1. / x.iter().sum::<f64>());
for (index, row) in rgb_to_camera.iter_mut().enumerate() {
*row = row.map(|x| x * white_balance_multiplier[index]);
}
let camera_to_rgb = transpose(pseudoinverse(rgb_to_camera));
let cfa_white_balance_multiplier = if let Some(white_balance) = self.camera_white_balance {
white_balance
} else {
self.cfa_pattern.map(|index| white_balance_multiplier[index as usize])
};
self.white_balance = Some(cfa_white_balance_multiplier);
self.camera_to_rgb = Some(camera_to_rgb);
}
}
#[allow(clippy::needless_range_loop)]
fn pseudoinverse<const N: usize>(matrix: [[f64; 3]; N]) -> [[f64; 3]; N] {
let mut output_matrix = [[0.; 3]; N];
let mut work = [[0.; 6]; 3];
for i in 0..3 {
for j in 0..6 {
work[i][j] = if j == i + 3 { 1. } else { 0. };
}
for j in 0..3 {
for k in 0..N {
work[i][j] += matrix[k][i] * matrix[k][j];
}
}
}
for i in 0..3 {
let num = work[i][i];
for j in 0..6 {
work[i][j] /= num;
}
for k in 0..3 {
if k == i {
continue;
}
let num = work[k][i];
for j in 0..6 {
work[k][j] -= work[i][j] * num;
}
}
}
for i in 0..N {
for j in 0..3 {
output_matrix[i][j] = 0.;
for k in 0..3 {
output_matrix[i][j] += work[j][k + 3] * matrix[i][k];
}
}
}
output_matrix
}
fn transpose<const N: usize>(matrix: [[f64; 3]; N]) -> [[f64; N]; 3] {
let mut output_matrix = [[0.; N]; 3];
for (i, row) in matrix.iter().enumerate() {
for (j, &value) in row.iter().enumerate() {
output_matrix[j][i] = value;
}
}
output_matrix
}

View File

@@ -0,0 +1,60 @@
use crate::tiff::file::TiffRead;
use crate::tiff::tags::{Make, Model, Tag};
use crate::tiff::{Ifd, TiffError};
use rawkit_proc_macros::Tag;
use std::io::{Read, Seek};
const COMPANY_NAMES: [&str; 22] = [
"AgfaPhoto",
"Canon",
"Casio",
"Epson",
"Fujifilm",
"Mamiya",
"Minolta",
"Motorola",
"Kodak",
"Konica",
"Leica",
"Nikon",
"Nokia",
"Olympus",
"Ricoh",
"Pentax",
"Phase One",
"Samsung",
"Sigma",
"Sinar",
"Sony",
"YI",
];
#[allow(dead_code)]
#[derive(Tag)]
struct CameraModelIfd {
make: Make,
model: Model,
}
pub struct CameraModel {
pub make: String,
pub model: String,
}
pub fn identify_camera_model<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Option<CameraModel> {
let mut ifd = ifd.get_value::<CameraModelIfd, _>(file).unwrap();
ifd.make.make_ascii_lowercase();
for company_name in COMPANY_NAMES {
let lowercase_company_name = company_name.to_ascii_lowercase();
if ifd.make.contains(&lowercase_company_name) {
return Some(CameraModel {
make: company_name.to_string(),
model: ifd.model,
});
}
}
None
}

View File

@@ -0,0 +1,2 @@
pub mod camera_data;
pub mod identify;

View File

@@ -0,0 +1,13 @@
use crate::{Pixel, RawImage, CHANNELS_IN_RGB};
impl RawImage {
pub fn convert_to_rgb_fn(&self) -> impl Fn(Pixel) -> [u16; CHANNELS_IN_RGB] {
let Some(camera_to_rgb) = self.camera_to_rgb else { todo!() };
move |pixel: Pixel| {
std::array::from_fn(|i| i)
.map(|i| camera_to_rgb[i].iter().zip(pixel.values.iter()).map(|(&coeff, &value)| coeff * value as f64).sum())
.map(|x: f64| (x as u16).clamp(0, u16::MAX))
}
}
}

View File

@@ -0,0 +1,84 @@
use crate::{Histogram, Image, Pixel, CHANNELS_IN_RGB};
use std::f64::consts::E;
impl Image<u16> {
pub fn gamma_correction_fn(&self, histogram: &Histogram) -> impl Fn(Pixel) -> [u16; CHANNELS_IN_RGB] {
let percentage = self.width * self.height;
let mut white = 0;
for channel_histogram in histogram {
let mut total = 0;
for i in (0x20..0x2000).rev() {
total += channel_histogram[i] as u64;
if total * 100 > percentage as u64 {
white = white.max(i);
break;
}
}
}
let curve = generate_gamma_curve(0.45, 4.5, (white << 3) as f64);
move |pixel: Pixel| pixel.values.map(|value| curve[value as usize])
}
}
/// `max_intensity` must be non-zero.
fn generate_gamma_curve(power: f64, threshold: f64, max_intensity: f64) -> Vec<u16> {
debug_assert!(max_intensity != 0.);
let (mut bound_start, mut bound_end) = if threshold >= 1. { (0., 1.) } else { (1., 0.) };
let mut transition_point = 0.;
let mut transition_ratio = 0.;
let mut curve_adjustment = 0.;
if threshold != 0. && (threshold - 1.) * (power - 1.) <= 0. {
for _ in 0..48 {
transition_point = (bound_start + bound_end) / 2.;
if power != 0. {
let temp_transition_ratio = transition_point / threshold;
let exponential_power = temp_transition_ratio.powf(-power);
let normalized_exponential_power = (exponential_power - 1.) / power;
let comparison_result = normalized_exponential_power - (1. / transition_point);
let bound_to_update = if comparison_result > -1. { &mut bound_end } else { &mut bound_start };
*bound_to_update = transition_point;
} else {
let adjusted_transition_point = E.powf(1. - 1. / transition_point);
let transition_point_ratio = transition_point / adjusted_transition_point;
let bound_to_update = if transition_point_ratio < threshold { &mut bound_end } else { &mut bound_start };
*bound_to_update = transition_point;
}
}
transition_ratio = transition_point / threshold;
if power != 0. {
curve_adjustment = transition_point * ((1. / power) - 1.);
}
}
let mut curve = vec![0xffff; 0x1_0000];
let length = curve.len() as f64;
for (i, entry) in curve.iter_mut().enumerate() {
let ratio = (i as f64) / max_intensity;
if ratio < 1. {
let altered_ratio = if ratio < transition_ratio {
ratio * threshold
} else if power != 0. {
ratio.powf(power) * (1. + curve_adjustment) - curve_adjustment
} else {
ratio.ln() * transition_point + 1.
};
*entry = (length * altered_ratio) as u16;
}
}
curve
}

View File

@@ -0,0 +1,4 @@
pub mod convert_to_rgb;
pub mod gamma_correction;
pub mod record_histogram;
pub mod transform;

View File

@@ -0,0 +1,29 @@
use crate::{Histogram, Pixel, PixelTransform, RawImage, CHANNELS_IN_RGB};
impl RawImage {
pub fn record_histogram_fn(&self) -> RecordHistogram {
RecordHistogram::new()
}
}
pub struct RecordHistogram {
pub histogram: Histogram,
}
impl RecordHistogram {
fn new() -> RecordHistogram {
RecordHistogram {
histogram: [[0; 0x2000]; CHANNELS_IN_RGB],
}
}
}
impl PixelTransform for &mut RecordHistogram {
fn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {
self.histogram
.iter_mut()
.zip(pixel.values.iter())
.for_each(|(histogram, &value)| histogram[value as usize >> CHANNELS_IN_RGB] += 1);
pixel.values
}
}

View File

@@ -0,0 +1,70 @@
use crate::{Image, Pixel, Transform};
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() {
(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 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);
let mut index = index_0_0;
let channels = self.channels as usize;
(
final_width,
final_height,
(0..final_height).flat_map(move |row| {
let temp = (0..final_width).map(move |column| {
let initial_index = (self.width as i64 * index.0 + index.1) as usize;
let pixel = &self.data[channels * initial_index..channels * (initial_index + 1)];
index = (index.0 + column_step.0, index.1 + column_step.1);
Pixel {
values: pixel.try_into().unwrap(),
row,
column,
}
});
index = (index.0 + row_step.0, index.1 + row_step.1);
temp
}),
)
}
}
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,
};
if value & 4 != 0 {
std::mem::swap(&mut row, &mut column)
}
if value & 2 != 0 {
row = height - 1 - row;
}
if value & 1 != 0 {
column = width - 1 - column;
}
(row as i64, column as i64)
}

View File

@@ -0,0 +1,3 @@
pub mod scale_to_16bit;
pub mod scale_white_balance;
pub mod subtract_black;

View File

@@ -0,0 +1,15 @@
use crate::{RawImage, RawPixel, SubtractBlack};
impl RawImage {
pub fn scale_to_16bit_fn(&self) -> impl Fn(RawPixel) -> u16 {
let black_level = match self.black {
SubtractBlack::CfaGrid(x) => x,
_ => unreachable!(),
};
let maximum = self.maximum - black_level.iter().max().unwrap();
let scale_to_16bit_multiplier = if maximum > 0 { u16::MAX as f64 / maximum as f64 } else { 1. };
move |pixel: RawPixel| ((pixel.value as f64) * scale_to_16bit_multiplier).min(u16::MAX as f64).max(0.) as u16
}
}

View File

@@ -0,0 +1,31 @@
use crate::{RawImage, RawPixel};
impl RawImage {
pub fn scale_white_balance_fn(&self) -> impl Fn(RawPixel) -> u16 {
let Some(mut white_balance) = self.white_balance else { todo!() };
if white_balance[1] == 0. {
white_balance[1] = 1.;
}
// TODO: Move this at its correct location when highlights are implemented correctly.
let highlight = 0;
let normalization_factor = if highlight == 0 {
white_balance.into_iter().fold(f64::INFINITY, f64::min)
} else {
white_balance.into_iter().fold(f64::NEG_INFINITY, f64::max)
};
let normalized_white_balance = if normalization_factor > 0.00001 {
white_balance.map(|x| x / normalization_factor)
} else {
[1., 1., 1., 1.]
};
move |pixel: RawPixel| {
let cfa_index = 2 * (pixel.row % 2) + (pixel.column % 2);
((pixel.value as f64) * normalized_white_balance[cfa_index]).min(u16::MAX as f64).max(0.) as u16
}
}
}

View File

@@ -0,0 +1,11 @@
use crate::RawPixel;
use crate::{RawImage, SubtractBlack};
impl RawImage {
pub fn subtract_black_fn(&self) -> impl Fn(RawPixel) -> u16 {
match self.black {
SubtractBlack::CfaGrid(black_levels) => move |pixel: RawPixel| pixel.value.saturating_sub(black_levels[2 * (pixel.row % 2) + (pixel.column % 2)]),
_ => todo!(),
}
}
}

View File

@@ -0,0 +1,83 @@
use crate::CHANNELS_IN_RGB;
#[derive(Clone, Copy)]
pub struct RawPixel {
pub value: u16,
pub row: usize,
pub column: usize,
}
#[derive(Clone, Copy)]
pub struct Pixel {
pub values: [u16; CHANNELS_IN_RGB],
pub row: usize,
pub column: usize,
}
pub trait RawPixelTransform {
fn apply(&mut self, pixel: RawPixel) -> u16;
}
impl<T: Fn(RawPixel) -> u16> RawPixelTransform for T {
fn apply(&mut self, pixel: RawPixel) -> u16 {
self(pixel)
}
}
macro_rules! impl_raw_pixel_transform {
($($idx:tt $t:tt),+) => {
impl<$($t,)+> RawPixelTransform for ($($t,)+)
where
$($t: RawPixelTransform,)+
{
fn apply(&mut self, mut pixel: RawPixel) -> u16 {
$(pixel.value = self.$idx.apply(pixel);)*
pixel.value
}
}
};
}
impl_raw_pixel_transform!(0 A);
impl_raw_pixel_transform!(0 A, 1 B);
impl_raw_pixel_transform!(0 A, 1 B, 2 C);
impl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D);
impl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E);
impl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
impl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
impl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
pub trait PixelTransform {
fn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB];
}
impl<T: Fn(Pixel) -> [u16; CHANNELS_IN_RGB]> PixelTransform for T {
fn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {
self(pixel)
}
}
macro_rules! impl_pixel_transform {
($($idx:tt $t:tt),+) => {
impl<$($t,)+> PixelTransform for ($($t,)+)
where
$($t: PixelTransform,)+
{
fn apply(&mut self, mut pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {
$(pixel.values = self.$idx.apply(pixel);)*
pixel.values
}
}
};
}
impl_pixel_transform!(0 A);
impl_pixel_transform!(0 A, 1 B);
impl_pixel_transform!(0 A, 1 B, 2 C);
impl_pixel_transform!(0 A, 1 B, 2 C, 3 D);
impl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E);
impl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
impl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
impl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);

View File

@@ -0,0 +1,152 @@
use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Endian {
Little,
Big,
}
pub struct TiffRead<R: Read + Seek> {
reader: R,
endian: Endian,
}
impl<R: Read + Seek> TiffRead<R> {
pub fn new(mut reader: R) -> Result<Self> {
let error = Error::new(ErrorKind::InvalidData, "Invalid Tiff format");
let mut data = [0_u8; 2];
reader.read_exact(&mut data)?;
let endian = if data[0] == 0x49 && data[1] == 0x49 {
Endian::Little
} else if data[0] == 0x4d && data[1] == 0x4d {
Endian::Big
} else {
return Err(error);
};
reader.read_exact(&mut data)?;
let magic_number = match endian {
Endian::Little => u16::from_le_bytes(data),
Endian::Big => u16::from_be_bytes(data),
};
if magic_number != 42 {
return Err(error);
}
Ok(Self { reader, endian })
}
pub fn endian(&self) -> Endian {
self.endian
}
}
impl<R: Read + Seek> Read for TiffRead<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.reader.read(buf)
}
}
impl<R: Read + Seek> Seek for TiffRead<R> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
self.reader.seek(pos)
}
}
impl<R: Read + Seek> TiffRead<R> {
pub fn seek_from_start(&mut self, offset: u32) -> Result<u64> {
self.reader.seek(SeekFrom::Start(offset.into()))
}
pub fn read_ascii(&mut self) -> Result<char> {
let data = self.read_n::<1>()?;
Ok(data[0] as char)
}
pub fn read_n<const N: usize>(&mut self) -> Result<[u8; N]> {
let mut data = [0_u8; N];
self.read_exact(&mut data)?;
Ok(data)
}
pub fn read_u8(&mut self) -> Result<u8> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(u8::from_le_bytes(data)),
Endian::Big => Ok(u8::from_be_bytes(data)),
}
}
pub fn read_u16(&mut self) -> Result<u16> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(u16::from_le_bytes(data)),
Endian::Big => Ok(u16::from_be_bytes(data)),
}
}
pub fn read_u32(&mut self) -> Result<u32> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(u32::from_le_bytes(data)),
Endian::Big => Ok(u32::from_be_bytes(data)),
}
}
pub fn read_u64(&mut self) -> Result<u64> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(u64::from_le_bytes(data)),
Endian::Big => Ok(u64::from_be_bytes(data)),
}
}
pub fn read_i8(&mut self) -> Result<i8> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(i8::from_le_bytes(data)),
Endian::Big => Ok(i8::from_be_bytes(data)),
}
}
pub fn read_i16(&mut self) -> Result<i16> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(i16::from_le_bytes(data)),
Endian::Big => Ok(i16::from_be_bytes(data)),
}
}
pub fn read_i32(&mut self) -> Result<i32> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(i32::from_le_bytes(data)),
Endian::Big => Ok(i32::from_be_bytes(data)),
}
}
pub fn read_i64(&mut self) -> Result<i64> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(i64::from_le_bytes(data)),
Endian::Big => Ok(i64::from_be_bytes(data)),
}
}
pub fn read_f32(&mut self) -> Result<f32> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(f32::from_le_bytes(data)),
Endian::Big => Ok(f32::from_be_bytes(data)),
}
}
pub fn read_f64(&mut self) -> Result<f64> {
let data = self.read_n()?;
match self.endian {
Endian::Little => Ok(f64::from_le_bytes(data)),
Endian::Big => Ok(f64::from_be_bytes(data)),
}
}
}

View File

@@ -0,0 +1,172 @@
pub mod file;
pub mod tags;
mod types;
pub mod values;
use file::TiffRead;
use tags::Tag;
use num_enum::{FromPrimitive, IntoPrimitive};
use std::fmt::Display;
use std::io::{Read, Seek};
use thiserror::Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
#[repr(u16)]
pub enum TagId {
ImageWidth = 0x100,
ImageLength = 0x101,
BitsPerSample = 0x102,
Compression = 0x103,
PhotometricInterpretation = 0x104,
Make = 0x10f,
Model = 0x110,
StripOffsets = 0x111,
Orientation = 0x112,
SamplesPerPixel = 0x115,
RowsPerStrip = 0x116,
StripByteCounts = 0x117,
SubIfd = 0x14a,
JpegOffset = 0x201,
JpegLength = 0x202,
SonyToneCurve = 0x7010,
BlackLevel = 0x7310,
WhiteBalanceRggbLevels = 0x7313,
CfaPatternDim = 0x828d,
CfaPattern = 0x828e,
ColorMatrix1 = 0xc621,
ColorMatrix2 = 0xc622,
#[num_enum(catch_all)]
Unknown(u16),
}
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
pub enum IfdTagType {
Byte = 1,
Ascii = 2,
Short = 3,
Long = 4,
Rational = 5,
SByte = 6,
Undefined = 7,
SShort = 8,
SLong = 9,
SRational = 10,
Float = 11,
Double = 12,
#[num_enum(catch_all)]
Unknown(u16),
}
#[derive(Copy, Clone, Debug)]
pub struct IfdEntry {
tag: TagId,
the_type: IfdTagType,
count: u32,
value: u32,
}
#[derive(Clone, Debug)]
pub struct Ifd {
current_ifd_offset: u32,
ifd_entries: Vec<IfdEntry>,
next_ifd_offset: Option<u32>,
}
impl Ifd {
pub fn new_first_ifd<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self, TiffError> {
file.seek_from_start(4)?;
let current_ifd_offset = file.read_u32()?;
Ifd::new_from_offset(file, current_ifd_offset)
}
pub fn new_from_offset<R: Read + Seek>(file: &mut TiffRead<R>, offset: u32) -> Result<Self, TiffError> {
if offset == 0 {
return Err(TiffError::InvalidOffset);
}
file.seek_from_start(offset)?;
let num = file.read_u16()?;
let mut ifd_entries = Vec::with_capacity(num.into());
for _ in 0..num {
let tag = file.read_u16()?.into();
let the_type = file.read_u16()?.into();
let count = file.read_u32()?;
let value = file.read_u32()?;
ifd_entries.push(IfdEntry { tag, the_type, count, value });
}
let next_ifd_offset = file.read_u32()?;
let next_ifd_offset = if next_ifd_offset == 0 { None } else { Some(next_ifd_offset) };
Ok(Ifd {
current_ifd_offset: offset,
ifd_entries,
next_ifd_offset,
})
}
fn _next_ifd<R: Read + Seek>(&self, file: &mut TiffRead<R>) -> Result<Self, TiffError> {
Ifd::new_from_offset(file, self.next_ifd_offset.unwrap_or(0))
}
pub fn ifd_entries(&self) -> &[IfdEntry] {
&self.ifd_entries
}
pub fn iter(&self) -> impl Iterator<Item = &IfdEntry> {
self.ifd_entries.iter()
}
pub fn get_value<T: Tag, R: Read + Seek>(&self, file: &mut TiffRead<R>) -> Result<T::Output, TiffError> {
T::get(self, file)
}
}
impl Display for Ifd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("IFD offset: ")?;
self.current_ifd_offset.fmt(f)?;
f.write_str("\n")?;
for ifd_entry in self.ifd_entries() {
f.write_fmt(format_args!(
"|- Tag: {:x?}, Type: {:?}, Count: {}, Value: {:x}\n",
ifd_entry.tag, ifd_entry.the_type, ifd_entry.count, ifd_entry.value
))?;
}
f.write_str("Next IFD offset: ")?;
if let Some(offset) = self.next_ifd_offset {
offset.fmt(f)?;
} else {
f.write_str("None")?;
}
f.write_str("\n")?;
Ok(())
}
}
#[derive(Error, Debug)]
pub enum TiffError {
#[error("The value was invalid")]
InvalidValue,
#[error("The type was invalid")]
InvalidType,
#[error("The count was invalid")]
InvalidCount,
#[error("The tag was missing")]
MissingTag,
#[error("The offset was invalid or zero")]
InvalidOffset,
#[error("An error occurred when converting integer from one type to another")]
ConversionError(#[from] std::num::TryFromIntError),
#[error("An IO Error ocurred")]
IoError(#[from] std::io::Error),
}

View File

@@ -0,0 +1,228 @@
use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeOrientation, TypeSRational, TypeSShort, TypeShort, TypeSonyToneCurve, TypeString};
use super::{Ifd, TagId, TiffError, TiffRead};
use std::io::{Read, Seek};
pub trait SimpleTag {
type Type: TagType;
const ID: TagId;
const NAME: &'static str;
}
pub struct ImageWidth;
pub struct ImageLength;
pub struct BitsPerSample;
pub struct Compression;
pub struct PhotometricInterpretation;
pub struct Make;
pub struct Model;
pub struct StripOffsets;
pub struct Orientation;
pub struct SamplesPerPixel;
pub struct RowsPerStrip;
pub struct StripByteCounts;
pub struct SubIfd;
pub struct JpegOffset;
pub struct JpegLength;
pub struct SonyDataOffset;
pub struct SonyToneCurve;
pub struct BlackLevel;
pub struct WhiteBalanceRggbLevels;
pub struct CfaPatternDim;
pub struct CfaPattern;
pub struct ColorMatrix1;
pub struct ColorMatrix2;
impl SimpleTag for ImageWidth {
type Type = TypeNumber;
const ID: TagId = TagId::ImageWidth;
const NAME: &'static str = "Image Width";
}
impl SimpleTag for ImageLength {
type Type = TypeNumber;
const ID: TagId = TagId::ImageLength;
const NAME: &'static str = "Image Length";
}
impl SimpleTag for BitsPerSample {
type Type = TypeShort;
const ID: TagId = TagId::BitsPerSample;
const NAME: &'static str = "Bits per Sample";
}
impl SimpleTag for Compression {
type Type = TypeShort;
const ID: TagId = TagId::Compression;
const NAME: &'static str = "Compression";
}
impl SimpleTag for PhotometricInterpretation {
type Type = TypeShort;
const ID: TagId = TagId::PhotometricInterpretation;
const NAME: &'static str = "Photometric Interpretation";
}
impl SimpleTag for Make {
type Type = TypeString;
const ID: TagId = TagId::Make;
const NAME: &'static str = "Make";
}
impl SimpleTag for Model {
type Type = TypeString;
const ID: TagId = TagId::Model;
const NAME: &'static str = "Model";
}
impl SimpleTag for StripOffsets {
type Type = Array<TypeNumber>;
const ID: TagId = TagId::StripOffsets;
const NAME: &'static str = "Strip Offsets";
}
impl SimpleTag for Orientation {
type Type = TypeOrientation;
const ID: TagId = TagId::Orientation;
const NAME: &'static str = "Orientation";
}
impl SimpleTag for SamplesPerPixel {
type Type = TypeShort;
const ID: TagId = TagId::SamplesPerPixel;
const NAME: &'static str = "Samples per Pixel";
}
impl SimpleTag for RowsPerStrip {
type Type = TypeNumber;
const ID: TagId = TagId::RowsPerStrip;
const NAME: &'static str = "Rows per Strip";
}
impl SimpleTag for StripByteCounts {
type Type = Array<TypeNumber>;
const ID: TagId = TagId::StripByteCounts;
const NAME: &'static str = "Strip Byte Counts";
}
impl SimpleTag for SubIfd {
type Type = TypeIfd;
const ID: TagId = TagId::SubIfd;
const NAME: &'static str = "SubIFD";
}
impl SimpleTag for JpegOffset {
type Type = TypeLong;
const ID: TagId = TagId::JpegOffset;
const NAME: &'static str = "Jpeg Offset";
}
impl SimpleTag for JpegLength {
type Type = TypeLong;
const ID: TagId = TagId::JpegLength;
const NAME: &'static str = "Jpeg Length";
}
impl SimpleTag for CfaPatternDim {
type Type = ConstArray<TypeShort, 2>;
const ID: TagId = TagId::CfaPatternDim;
const NAME: &'static str = "CFA Pattern Dimension";
}
impl SimpleTag for CfaPattern {
type Type = Array<TypeByte>;
const ID: TagId = TagId::CfaPattern;
const NAME: &'static str = "CFA Pattern";
}
impl SimpleTag for ColorMatrix1 {
type Type = Array<TypeSRational>;
const ID: TagId = TagId::ColorMatrix1;
const NAME: &'static str = "Color Matrix 1";
}
impl SimpleTag for ColorMatrix2 {
type Type = Array<TypeSRational>;
const ID: TagId = TagId::ColorMatrix2;
const NAME: &'static str = "Color Matrix 2";
}
impl SimpleTag for SonyDataOffset {
type Type = TypeLong;
const ID: TagId = TagId::SubIfd;
const NAME: &'static str = "Sony Data Offset";
}
impl SimpleTag for SonyToneCurve {
type Type = TypeSonyToneCurve;
const ID: TagId = TagId::SonyToneCurve;
const NAME: &'static str = "Sony Tone Curve";
}
impl SimpleTag for BlackLevel {
type Type = ConstArray<TypeShort, 4>;
const ID: TagId = TagId::BlackLevel;
const NAME: &'static str = "Black Level";
}
impl SimpleTag for WhiteBalanceRggbLevels {
type Type = ConstArray<TypeSShort, 4>;
const ID: TagId = TagId::WhiteBalanceRggbLevels;
const NAME: &'static str = "White Balance Levels (RGGB)";
}
pub trait Tag {
type Output;
fn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError>;
}
impl<T: SimpleTag> Tag for T {
type Output = <T::Type as TagType>::Output;
fn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let tag_id = T::ID;
let index: u32 = ifd.iter().position(|x| x.tag == tag_id).ok_or(TiffError::MissingTag)?.try_into()?;
file.seek_from_start(ifd.current_ifd_offset + 2 + 12 * index + 2)?;
T::Type::read(file)
}
}
impl<T: Tag> Tag for Option<T> {
type Output = Option<T::Output>;
fn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let result = T::get(ifd, file);
match result {
Err(TiffError::MissingTag) => Ok(None),
Ok(x) => Ok(Some(x)),
Err(x) => Err(x),
}
}
}

View File

@@ -0,0 +1,399 @@
use std::io::{Read, Seek};
use super::file::TiffRead;
use super::values::{CurveLookupTable, Rational, Transform};
use super::{Ifd, IfdTagType, TiffError};
pub struct TypeAscii;
pub struct TypeByte;
pub struct TypeShort;
pub struct TypeLong;
pub struct TypeRational;
pub struct TypeSByte;
pub struct TypeSShort;
pub struct TypeSLong;
pub struct TypeSRational;
pub struct TypeFloat;
pub struct TypeDouble;
pub struct TypeUndefined;
pub struct TypeNumber;
pub struct TypeSNumber;
pub struct TypeIfd;
pub trait PrimitiveType {
type Output;
fn get_size(the_type: IfdTagType) -> Option<u32>;
fn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError>;
}
impl PrimitiveType for TypeAscii {
type Output = char;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Ascii => Some(1),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let value = file.read_ascii()?;
if value.is_ascii() {
Ok(value)
} else {
Err(TiffError::InvalidValue)
}
}
}
impl PrimitiveType for TypeByte {
type Output = u8;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Byte => Some(1),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_u8()?)
}
}
impl PrimitiveType for TypeShort {
type Output = u16;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Short => Some(2),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_u16()?)
}
}
impl PrimitiveType for TypeLong {
type Output = u32;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Long => Some(4),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_u32()?)
}
}
impl PrimitiveType for TypeRational {
type Output = Rational<u32>;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Rational => Some(8),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let numerator = TypeLong::read_primitive(the_type, file)?;
let denominator = TypeLong::read_primitive(the_type, file)?;
Ok(Rational { numerator, denominator })
}
}
impl PrimitiveType for TypeSByte {
type Output = i8;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::SByte => Some(1),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_i8()?)
}
}
impl PrimitiveType for TypeSShort {
type Output = i16;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::SShort => Some(2),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_i16()?)
}
}
impl PrimitiveType for TypeSLong {
type Output = i32;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::SLong => Some(4),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_i32()?)
}
}
impl PrimitiveType for TypeSRational {
type Output = Rational<i32>;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::SRational => Some(8),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let numerator = TypeSLong::read_primitive(the_type, file)?;
let denominator = TypeSLong::read_primitive(the_type, file)?;
Ok(Rational { numerator, denominator })
}
}
impl PrimitiveType for TypeFloat {
type Output = f32;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Float => Some(4),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_f32()?)
}
}
impl PrimitiveType for TypeDouble {
type Output = f64;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Double => Some(8),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(file.read_f64()?)
}
}
impl PrimitiveType for TypeUndefined {
type Output = ();
fn get_size(_: IfdTagType) -> Option<u32> {
todo!()
}
fn read_primitive<R: Read + Seek>(_: IfdTagType, _: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
todo!()
}
}
impl PrimitiveType for TypeNumber {
type Output = u32;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::Byte => TypeByte::get_size(the_type),
IfdTagType::Short => TypeShort::get_size(the_type),
IfdTagType::Long => TypeLong::get_size(the_type),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(match the_type {
IfdTagType::Byte => TypeByte::read_primitive(the_type, file)?.into(),
IfdTagType::Short => TypeShort::read_primitive(the_type, file)?.into(),
IfdTagType::Long => TypeLong::read_primitive(the_type, file)?,
_ => unreachable!(),
})
}
}
impl PrimitiveType for TypeSNumber {
type Output = i32;
fn get_size(the_type: IfdTagType) -> Option<u32> {
match the_type {
IfdTagType::SByte => TypeSByte::get_size(the_type),
IfdTagType::SShort => TypeSShort::get_size(the_type),
IfdTagType::SLong => TypeSLong::get_size(the_type),
_ => None,
}
}
fn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
Ok(match the_type {
IfdTagType::SByte => TypeSByte::read_primitive(the_type, file)?.into(),
IfdTagType::SShort => TypeSShort::read_primitive(the_type, file)?.into(),
IfdTagType::SLong => TypeSLong::read_primitive(the_type, file)?,
_ => unreachable!(),
})
}
}
impl PrimitiveType for TypeIfd {
type Output = Ifd;
fn get_size(the_type: IfdTagType) -> Option<u32> {
TypeLong::get_size(the_type)
}
fn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let offset = TypeLong::read_primitive(the_type, file)?;
Ifd::new_from_offset(file, offset)
}
}
pub trait TagType {
type Output;
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError>;
}
impl<T: PrimitiveType> TagType for T {
type Output = T::Output;
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let the_type = IfdTagType::from(file.read_u16()?);
let count = file.read_u32()?;
if count != 1 {
return Err(TiffError::InvalidCount);
}
let size = T::get_size(the_type).ok_or(TiffError::InvalidType)?;
if count * size > 4 {
let offset = file.read_u32()?;
file.seek_from_start(offset)?;
}
T::read_primitive(the_type, file)
}
}
pub struct Array<T: PrimitiveType> {
primitive_type: std::marker::PhantomData<T>,
}
pub struct ConstArray<T: PrimitiveType, const N: usize> {
primitive_type: std::marker::PhantomData<T>,
}
impl<T: PrimitiveType> TagType for Array<T> {
type Output = Vec<T::Output>;
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let the_type = IfdTagType::from(file.read_u16()?);
let count = file.read_u32()?;
let size = T::get_size(the_type).ok_or(TiffError::InvalidType)?;
if count * size > 4 {
let offset = file.read_u32()?;
file.seek_from_start(offset)?;
}
let mut ans = Vec::with_capacity(count.try_into()?);
for _ in 0..count {
ans.push(T::read_primitive(the_type, file)?);
}
Ok(ans)
}
}
impl<T: PrimitiveType, const N: usize> TagType for ConstArray<T, N> {
type Output = [T::Output; N];
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let the_type = IfdTagType::from(file.read_u16()?);
let count = file.read_u32()?;
if count != N.try_into()? {
return Err(TiffError::InvalidCount);
}
let size = T::get_size(the_type).ok_or(TiffError::InvalidType)?;
if count * size > 4 {
let offset = file.read_u32()?;
file.seek_from_start(offset)?;
}
let mut ans = Vec::with_capacity(count.try_into()?);
for _ in 0..count {
ans.push(T::read_primitive(the_type, file)?);
}
ans.try_into().map_err(|_| TiffError::InvalidCount)
}
}
pub struct TypeString;
pub struct TypeSonyToneCurve;
pub struct TypeOrientation;
impl TagType for TypeString {
type Output = String;
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let string = Array::<TypeAscii>::read(file)?;
// Skip the NUL character at the end
let len = string.len();
Ok(string.into_iter().take(len - 1).collect())
}
}
impl TagType for TypeSonyToneCurve {
type Output = CurveLookupTable;
fn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {
let values = ConstArray::<TypeShort, 4>::read(file)?;
Ok(CurveLookupTable::from_sony_tone_table(values))
}
}
impl TagType for TypeOrientation {
type Output = Transform;
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),
})
}
}

View File

@@ -0,0 +1,79 @@
pub trait ToFloat {
fn to_float(&self) -> f64;
}
impl ToFloat for u32 {
fn to_float(&self) -> f64 {
*self as f64
}
}
impl ToFloat for i32 {
fn to_float(&self) -> f64 {
*self as f64
}
}
pub struct Rational<T: ToFloat> {
pub numerator: T,
pub denominator: T,
}
impl<T: ToFloat> ToFloat for Rational<T> {
fn to_float(&self) -> f64 {
self.numerator.to_float() / self.denominator.to_float()
}
}
pub struct CurveLookupTable {
table: Vec<u16>,
}
impl CurveLookupTable {
pub fn from_sony_tone_table(values: [u16; 4]) -> CurveLookupTable {
let mut sony_curve = [0, 0, 0, 0, 0, 4095];
for i in 0..4 {
sony_curve[i + 1] = values[i] >> 2 & 0xfff;
}
let mut table = vec![0_u16; (sony_curve[5] + 1).into()];
for i in 0..5 {
for j in (sony_curve[i] + 1)..=sony_curve[i + 1] {
table[j as usize] = table[(j - 1) as usize] + (1 << i);
}
}
CurveLookupTable { table }
}
pub fn get(&self, x: usize) -> u16 {
self.table[x]
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Transform {
Horizontal,
MirrorHorizontal,
Rotate180,
MirrorVertical,
MirrorHorizontalRotate270,
Rotate90,
MirrorHorizontalRotate90,
Rotate270,
}
impl Transform {
pub fn is_identity(&self) -> bool {
*self == Transform::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,
}
}
}