Files
DarkRoom/third_party/rawler-0.7.2/src/decoders/qtk.rs
T
dtourolle 77a1925bac Vendor rawler 0.7.2 unmodified
The crate as crates.io publishes it, minus .cargo-ok, its Cargo.lock and
data/testdata (13 MB of sample files only its own tests read). Not yet
routed through [patch.crates-io]; the next commit is the patch.
2026-09-27 17:33:19 -04:00

241 lines
8.2 KiB
Rust

// SPDX-License-Identifier: LGPL-2.1
// Copyright 2024 Daniel Vogelbacher <daniel@chaospixel.com>
// Originally written in C in dcraw.c by Dave Coffin
use rayon::iter::IndexedParallelIterator;
use rayon::iter::ParallelIterator;
use std::mem::swap;
use std::ops::Not;
use crate::Orientation;
use crate::RawImage;
use crate::RawLoader;
use crate::Result;
use crate::alloc_image_ok;
use crate::analyze::FormatDump;
use crate::bits::BEu16;
use crate::bits::Endian;
use crate::bits::LookupTable;
use crate::buffer::PaddedBuf;
use crate::exif::Exif;
use crate::pixarray::PixU16;
use crate::pumps::BitPump;
use crate::pumps::BitPumpMSB;
use crate::pumps::ByteStream;
use crate::rawsource::RawSource;
use super::Camera;
use super::Decoder;
use super::FormatHint;
use super::RawDecodeParams;
use super::RawMetadata;
use super::ok_cfa_image;
#[derive(Debug, Clone)]
pub struct QtkDecoder<'a> {
#[allow(unused)]
rawloader: &'a RawLoader,
camera: Camera,
}
pub fn is_qtk(file: &RawSource) -> bool {
match file.subview(0, 4) {
Ok(buf) => buf[0..4] == b"qktk"[..] || buf[0..4] == b"qktn"[..],
Err(_) => false,
}
}
impl<'a> QtkDecoder<'a> {
pub fn new(file: &RawSource, rawloader: &'a RawLoader) -> Result<QtkDecoder<'a>> {
match file.subview(0, 4)? {
b"qktk" => {
let make = "Apple";
let model = "QuickTake 100";
let camera = rawloader.check_supported_with_everything(make, model, "")?;
Ok(QtkDecoder { rawloader, camera })
}
b"qktn" => {
if file.subview(0, 6)?[5] != 0 {
let make = "Apple";
let model = "QuickTake 200";
let camera = rawloader.check_supported_with_everything(make, model, "")?;
Ok(QtkDecoder { rawloader, camera })
} else {
let make = "Apple";
let model = "QuickTake 150";
let camera = rawloader.check_supported_with_everything(make, model, "")?;
Ok(QtkDecoder { rawloader, camera })
}
}
sig => Err(crate::RawlerError::DecoderFailed(format!(
"Unable to use QTK decoder on file with signature: '{:?}'",
sig
))),
}
}
}
impl<'a> Decoder for QtkDecoder<'a> {
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
const META_OFFSET: u64 = 544;
let meta = file.subview(META_OFFSET, 16)?;
let mut stream = ByteStream::new(meta, Endian::Big);
let mut height = stream.get_u16() as usize;
let mut width = stream.get_u16() as usize;
let _zero = stream.get_u32();
let hint = stream.get_u16();
let offset = if hint == 30 { 738 } else { 736 };
let mut orientation = Orientation::Normal;
if height > width {
swap(&mut width, &mut height);
let info = file.subview(offset - 6, 6)?;
orientation = if BEu16(info, 0).not() & 3 > 0 {
Orientation::Rotate90
} else {
Orientation::Rotate270
};
log::debug!("QTK file has flipped width/height, new orientation: {:?}", orientation);
}
log::debug!("QTK file w: {}, h: {}, hint: {}", width, height, hint);
let src = file.subview_until_eof_padded(offset as u64)?;
let image = match file.subview(0, 4)? {
b"qktk" => Self::decompress_quicktake_100(self, &src, width, height, dummy)?,
b"qktn" => Self::decompress_quicktake_150(self, &src, width, height, dummy)?,
_ => unreachable!(),
};
let cpp = 1;
ok_cfa_image(self.camera.clone(), cpp, self.get_wb()?, image, dummy).map(|mut image| {
image.orientation = orientation;
image
})
}
fn format_dump(&self) -> FormatDump {
todo!()
}
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
let meta = RawMetadata::new(&self.camera, Exif::default());
Ok(meta)
}
fn format_hint(&self) -> FormatHint {
FormatHint::QTK
}
}
impl<'a> QtkDecoder<'a> {
fn get_wb(&self) -> Result<[f32; 4]> {
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
}
pub fn decompress_quicktake_150(&self, src: &PaddedBuf, width: usize, height: usize, dummy: bool) -> Result<PixU16> {
// Model 150 always compress with cbpp=3
let cbpp = 3;
crate::decompressors::radc::decompress(src, width, height, cbpp, dummy)
}
pub fn decompress_quicktake_100(&self, src: &[u8], width: usize, height: usize, dummy: bool) -> Result<PixU16> {
assert!(width > height);
let mut out = alloc_image_ok!(width, height, dummy);
let mut pump = BitPumpMSB::new(src);
const GSTEP: [i16; 16] = [-89, -60, -44, -32, -22, -15, -8, -2, 2, 8, 15, 22, 32, 44, 60, 89];
const RSTEP: [[i16; 4]; 6] = [
[-3, -1, 1, 3],
[-5, -1, 1, 5],
[-8, -2, 2, 8],
[-13, -3, 3, 13],
[-19, -4, 4, 19],
[-28, -6, 6, 28],
];
const CURVE: [u16; 256] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 86, 88, 90, 92, 94, 97, 99, 101, 103, 105, 107, 110, 112, 114, 116, 118, 120, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 147, 149,
151, 153, 155, 158, 160, 162, 164, 166, 168, 171, 173, 175, 177, 179, 181, 184, 186, 188, 190, 192, 195, 197, 199, 201, 203, 205, 208, 210, 212, 214,
216, 218, 221, 223, 226, 230, 235, 239, 244, 248, 252, 257, 261, 265, 270, 274, 278, 283, 287, 291, 296, 300, 305, 309, 313, 318, 322, 326, 331, 335,
339, 344, 348, 352, 357, 361, 365, 370, 374, 379, 383, 387, 392, 396, 400, 405, 409, 413, 418, 422, 426, 431, 435, 440, 444, 448, 453, 457, 461, 466,
470, 474, 479, 483, 487, 492, 496, 500, 508, 519, 531, 542, 553, 564, 575, 587, 598, 609, 620, 631, 643, 654, 665, 676, 687, 698, 710, 721, 732, 743,
754, 766, 777, 788, 799, 810, 822, 833, 844, 855, 866, 878, 889, 900, 911, 922, 933, 945, 956, 967, 978, 989, 1001, 1012, 1023,
];
let mut pix = [[0x80_i16; 644]; 484];
for row in 2..(height + 2) {
let cstart = 2 + (row & 1);
let mut val = 0;
for col in (cstart..(width + 2)).step_by(2) {
val = (((pix[row - 1][col - 1] + 2 * pix[row - 1][col + 1] + pix[row][col - 2]) >> 2) + GSTEP[pump.get_bits(4) as usize]).clamp(0, 255);
pix[row][col] = val;
if col < 4 {
pix[row][col - 2] = val;
pix[row + 1][(!row) & 1] = val;
}
if row == 2 {
pix[row - 1][col + 1] = val;
pix[row - 1][col + 3] = val;
}
}
pix[row][width + 2 + (row & 1)] = val; // last column
}
for rb in 0..2 {
for row in ((2 + rb)..(height + 2)).step_by(2) {
for col in ((3 - (row & 1))..(width + 2)).step_by(2) {
let sharp = if row < 4 || col < 4 {
2
} else {
let val = (pix[row - 2][col] - pix[row][col - 2]).abs() as i32
+ (pix[row - 2][col] - pix[row - 2][col - 2]).abs() as i32
+ (pix[row][col - 2] - pix[row - 2][col - 2]).abs() as i32;
match val {
0..4 => 0,
4..8 => 1,
8..16 => 2,
16..32 => 3,
32..48 => 4,
_ => 5,
}
};
let val = (((pix[row - 2][col] + pix[row][col - 2]) >> 1) + RSTEP[sharp][pump.get_bits(2) as usize]).clamp(0, 255);
pix[row][col] = val;
if row < 4 {
pix[row - 2][col + 2] = val;
};
if col < 4 {
pix[row + 2][col - 2] = val;
};
}
}
}
for row in 2..(height + 2) {
for col in ((3 - (row & 1))..(width + 2)).step_by(2) {
let val = ((pix[row][col - 1] + (pix[row][col] << 2) + pix[row][col + 1]) >> 1) - 0x100;
pix[row][col] = val.clamp(0, 255);
}
}
let tbl = LookupTable::new_with_bits(&CURVE, 10);
out.par_pixel_rows_mut().enumerate().for_each(|(row, line)| {
let mut random = ((pix[row + 2][2] as u32) << 16) | (pix[row + 2][3]) as u32;
for (x, p) in line.iter_mut().zip(pix[row + 2][2..width + 2].iter()) {
*x = tbl.dither(*p as u16, &mut random);
//*x = CURVE[*p as usize]; // no dither
}
});
Ok(out)
}
}