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.
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::Result;
|
||||
use crate::bits::*;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Rational;
|
||||
use crate::packed::*;
|
||||
use crate::rawsource::RawSource;
|
||||
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::ok_cfa_image;
|
||||
|
||||
pub fn is_ari(file: &RawSource) -> bool {
|
||||
match file.subview(0, 4) {
|
||||
Ok(buf) => buf[0..4] == b"ARRI"[..],
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AriDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> AriDecoder<'a> {
|
||||
pub fn new(file: &RawSource, rawloader: &'a RawLoader) -> Result<AriDecoder<'a>> {
|
||||
let buffer = file.subview(668, 30)?;
|
||||
let model = String::from_utf8_lossy(buffer).split_terminator('\0').next().unwrap_or("").to_string();
|
||||
let camera = rawloader.check_supported_with_everything("ARRI", &model, "")?;
|
||||
Ok(AriDecoder { rawloader, camera })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for AriDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let buffer = file.subview(0, 100)?;
|
||||
let offset = LEu32(buffer, ArriRawTag::DataOffset as usize) as usize;
|
||||
let width = LEu32(buffer, ArriRawTag::Width as usize) as usize;
|
||||
let height = LEu32(buffer, ArriRawTag::Height as usize) as usize;
|
||||
let src = file.subview_until_eof_padded(offset as u64)?;
|
||||
|
||||
let image = if self.camera.find_hint("little-endian") {
|
||||
decode_12le(&src, width, height, dummy)
|
||||
} else {
|
||||
decode_12be_msb32(&src, width, height, dummy)
|
||||
};
|
||||
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, self.get_wb(file)?, image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> crate::analyze::FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let mut exif = Exif::default();
|
||||
let buffer = file.subview(0, 0x0a98)?; // max header
|
||||
exif.recommended_exposure_index = Some(LEu32(buffer, ArriRawTag::ExposureIndexASA as usize));
|
||||
exif.sensitivity_type = Some(2);
|
||||
let lens_model = char_slice_to_string(&buffer[ArriRawTag::LensModel as usize..ArriRawTag::LensModel as usize + 32]);
|
||||
exif.lens_model = lens_model.map(|s| s.trim().into());
|
||||
log::debug!("Lens model: {:?}", exif.lens_model);
|
||||
let exposure_time = LEu32(buffer, ArriRawTag::ExposureTime as usize);
|
||||
exif.exposure_time = Some(Rational::new(exposure_time, 1000000));
|
||||
let focal_len = LEu32(buffer, ArriRawTag::LensFocalLen as usize);
|
||||
exif.focal_length = Some(Rational::new(focal_len, 1000));
|
||||
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::ARI
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AriDecoder<'a> {
|
||||
fn get_wb(&self, file: &RawSource) -> Result<[f32; 4]> {
|
||||
let buffer = file.subview(0, 100 + 12)?;
|
||||
Ok([LEf32(buffer, 100), LEf32(buffer, 104), LEf32(buffer, 108), f32::NAN])
|
||||
}
|
||||
}
|
||||
|
||||
enum ArriRawTag {
|
||||
DataOffset = 0x0008,
|
||||
Width = 0x0014,
|
||||
Height = 0x0018,
|
||||
ExposureIndexASA = 0x0074,
|
||||
ExposureTime = 0x018C,
|
||||
LensFocalLen = 0x037C,
|
||||
LensModel = 0x0398,
|
||||
}
|
||||
|
||||
fn char_slice_to_string(buf: &[u8]) -> Option<String> {
|
||||
Some(buf.iter().take_while(|&&c| c != 0).map(|&c| char::from(c)).collect())
|
||||
}
|
||||
+797
@@ -0,0 +1,797 @@
|
||||
use std::cmp;
|
||||
use std::io::Cursor;
|
||||
|
||||
use image::DynamicImage;
|
||||
use log::debug;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image;
|
||||
use crate::bits::*;
|
||||
use crate::decoders::decode_threaded;
|
||||
use crate::decoders::decode_threaded_multiline;
|
||||
use crate::decompressors::ljpeg::LjpegDecompressor;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::imgop::yuv::interpolate_yuv;
|
||||
use crate::imgop::yuv::ycbcr_to_rgb;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::packed::decode_12le;
|
||||
use crate::packed::decode_14be_unpacked;
|
||||
use crate::packed::decode_16be;
|
||||
use crate::packed::decode_16le;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::pumps::BitPump;
|
||||
use crate::pumps::BitPumpLSB;
|
||||
use crate::pumps::BitPumpMSB;
|
||||
use crate::rawimage::BlackLevel;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawimage::WhiteLevel;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::ok_cfa_image;
|
||||
|
||||
const SONY_E_MOUNT: &str = "e-mount";
|
||||
const SONY_A_MOUNT: &str = "a-mount";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArwDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> ArwDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<ArwDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::Absolute, &[])?
|
||||
} else {
|
||||
log::warn!("ARW makernote not found");
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
|
||||
//makernote.dump::<ExifTag>(0).iter().for_each(|line| eprintln!("DUMP: {}", line));
|
||||
|
||||
Ok(ArwDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
makernote,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for ArwDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let data = self.tiff.find_ifds_with_tag(TiffCommonTag::StripOffsets);
|
||||
if data.is_empty() {
|
||||
if self.camera.model == "DSLR-A100" {
|
||||
return self.image_a100(file, dummy);
|
||||
} else {
|
||||
// try decoding as SRF
|
||||
return self.image_srf(file, dummy);
|
||||
}
|
||||
}
|
||||
let raw = data[0];
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let mut height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let count = fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts).force_usize(0);
|
||||
let compression = fetch_tiff_tag!(raw, TiffCommonTag::Compression).force_u32(0);
|
||||
let bps = if let Some(forced_bps) = &self.camera.bps {
|
||||
*forced_bps
|
||||
} else {
|
||||
fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).force_usize(0)
|
||||
};
|
||||
|
||||
let params = self.get_params(file)?;
|
||||
debug!("Params: {:?}", params);
|
||||
|
||||
//assert!(params.blacklevel.is_some());
|
||||
//assert!(params.whitelevel.is_some()); // DSC-R1 is SR2 format and has no whitelevel
|
||||
|
||||
let mut white = params.whitelevel.map(|x| x[0]);
|
||||
let mut black = params.blacklevel;
|
||||
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
let mut cpp = 1;
|
||||
|
||||
let image = match compression {
|
||||
1 => {
|
||||
if self.camera.model == "DSC-R1" {
|
||||
decode_14be_unpacked(src, width, height, dummy)
|
||||
} else {
|
||||
decode_16le(src, width, height, dummy)
|
||||
}
|
||||
}
|
||||
7 => {
|
||||
cpp = fetch_tiff_tag!(raw, TiffCommonTag::SamplesPerPixel).force_usize(0);
|
||||
// Starting with A-1, image is compressed in tiles with LJPEG92.
|
||||
// Data is RGGB for bayer readout and YCbCr for reduced resolution files.
|
||||
ArwDecoder::decode_ljpeg(&self.camera, file, raw, dummy)?
|
||||
}
|
||||
32767 => {
|
||||
if (width * height * bps) != count * 8 {
|
||||
height += 8;
|
||||
ArwDecoder::decode_arw1(src, width, height, dummy)
|
||||
} else {
|
||||
match bps {
|
||||
8 => {
|
||||
let curve = ArwDecoder::get_curve(raw)?;
|
||||
ArwDecoder::decode_arw2(src, width, height, &curve, dummy)
|
||||
}
|
||||
12 => {
|
||||
/*
|
||||
Some cameras like the A700 have an uncompressed mode where the output is 12bit and
|
||||
does not require any curve. For these all we need to do is set 12bit black and white
|
||||
points instead of the 14bit ones of the normal compressed 8bit -> 10bit -> 14bit mode.
|
||||
|
||||
We set these 12bit points by shifting down the 14bit points. It might make sense to
|
||||
have a separate camera mode instead but since the values seem good we don't bother.
|
||||
*/
|
||||
white = white.map(|x| x >> 2);
|
||||
black = black.map(|mut x| {
|
||||
x.iter_mut().for_each(|x| *x >>= 2);
|
||||
x
|
||||
});
|
||||
decode_12le(src, width, height, dummy)
|
||||
}
|
||||
_ => return Err(RawlerError::DecoderFailed(format!("ARW2: Don't know how to decode images with {} bps", bps))),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(RawlerError::DecoderFailed(format!("ARW: Don't know how to decode type {}", compression))),
|
||||
};
|
||||
|
||||
let blacklevel = black.map(|black| match cpp {
|
||||
1 => BlackLevel::new(&black, self.camera.cfa.width, self.camera.cfa.height, cpp),
|
||||
// For YUV data, the blacklevel needs to be multiplicated by 2
|
||||
3 => BlackLevel::new(&[black[0] * 2, black[0] * 2, black[0] * 2], 1, 1, cpp),
|
||||
_ => panic!("Unsupported cpp == {}", cpp),
|
||||
});
|
||||
let whitelevel = white.map(|white| WhiteLevel(vec![white as u32; cpp]));
|
||||
|
||||
let photometric = match cpp {
|
||||
1 => RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera)),
|
||||
3 => RawPhotometricInterpretation::LinearRaw,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let mut img = RawImage::new(self.camera.clone(), image, cpp, params.wb, photometric, blacklevel, whitelevel, dummy);
|
||||
|
||||
if cpp == 3 {
|
||||
// For debayer images, we assume WB coeffs already applied
|
||||
img.wb_coeffs = [1.0, 1.0, 1.0, f32::NAN];
|
||||
}
|
||||
|
||||
if let Some(raw_image_size) = self.get_raw_image_size(raw)? {
|
||||
log::debug!("Found SONYRAWIMAGESIZE tag, using as active_area");
|
||||
img.active_area = Some(raw_image_size);
|
||||
} else {
|
||||
img.active_area = self.camera.active_area.map(|area| Rect::new_with_borders(Dim2::new(width, height), &area));
|
||||
}
|
||||
img.crop_area = Rect::from_tiff(raw).or_else(|| self.camera.crop_area.map(|area| Rect::new_with_borders(Dim2::new(width, height), &area)));
|
||||
|
||||
log::debug!("raw dim: {}x{}", width, height);
|
||||
log::debug!("crop_area: {:?}", img.crop_area);
|
||||
log::debug!("active_area: {:?}", img.active_area);
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
/// Return the embedded JPEG preview
|
||||
/// Exiftool docs says there is a tag 0x2002 including the image, but this tag
|
||||
/// exists in none of the samples?! Instead, we can use the JPEG thumbnail
|
||||
/// tags which exists for most samples.
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let root = self.tiff.root_ifd();
|
||||
if let Some(preview_off) = root.get_entry(ExifTag::JPEGInterchangeFormat) {
|
||||
if let Some(preview_len) = root.get_entry(ExifTag::JPEGInterchangeFormatLength) {
|
||||
let buf = file.subview(preview_off.force_u64(0), preview_len.force_u64(0))?;
|
||||
let img = image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to read JPEG image: {:?}", err)))?;
|
||||
return Ok(Some(img));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> crate::analyze::FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let mut exif = Exif::new(self.tiff.root_ifd())?;
|
||||
exif.extend_from_ifd(self.get_exif()?)?; // TODO: is this required?
|
||||
let mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::ARW
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ArwDecoder<'a> {
|
||||
fn get_exif(&self) -> Result<&IFD> {
|
||||
self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(ExifTag::MakerNotes)
|
||||
.ok_or_else(|| "EXIF IFD not found".into())
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
// Try tag 0x9416
|
||||
if let Some(Entry {
|
||||
value: Value::Undefined(params),
|
||||
..
|
||||
}) = self.makernote.get_entry(ArwMakernoteTag::Tag_9416)
|
||||
{
|
||||
let dechiphered_9416 = sony_tag9cxx_decipher(params);
|
||||
let lens_id = LEu16(&dechiphered_9416, 0x004b);
|
||||
debug!("Lens Id tag: {}", lens_id);
|
||||
|
||||
let resolver = LensResolver::new()
|
||||
.with_camera(&self.camera)
|
||||
.with_lens_id((lens_id as u32, 0))
|
||||
.with_mounts(&[SONY_E_MOUNT.into(), SONY_A_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
|
||||
// Try tag 0x9050
|
||||
if let Some(Entry {
|
||||
value: Value::Undefined(params),
|
||||
..
|
||||
}) = self.makernote.get_entry(ArwMakernoteTag::Tag_9050)
|
||||
{
|
||||
if params.len() >= 263 + 2 {
|
||||
let dechiphered_9050 = sony_tag9cxx_decipher(params);
|
||||
let lens_id = LEu16(&dechiphered_9050, 263);
|
||||
debug!("Lens Id tag: {}", lens_id);
|
||||
|
||||
let resolver = LensResolver::new()
|
||||
.with_camera(&self.camera)
|
||||
.with_lens_id((lens_id as u32, 0))
|
||||
.with_mounts(&[SONY_E_MOUNT.into(), SONY_A_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
}
|
||||
|
||||
// Try tag 0x940C
|
||||
if let Some(Entry {
|
||||
value: Value::Undefined(params),
|
||||
..
|
||||
}) = self.makernote.get_entry(ArwMakernoteTag::Tag_940C)
|
||||
{
|
||||
let dechiphered_940c = sony_tag9cxx_decipher(params);
|
||||
let lens_id = LEu16(&dechiphered_940c, 9);
|
||||
debug!("Lens Id tag: {}", lens_id);
|
||||
|
||||
let resolver = LensResolver::new()
|
||||
.with_camera(&self.camera)
|
||||
.with_lens_id((lens_id as u32, 0))
|
||||
.with_mounts(&[SONY_E_MOUNT.into(), SONY_A_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn image_a100(&self, file: &RawSource, dummy: bool) -> Result<RawImage> {
|
||||
// We've caught the elusive A100 in the wild, a transitional format
|
||||
// between the simple sanity of the MRW custom format and the wordly
|
||||
// wonderfullness of the Tiff-based ARW format, let's shoot from the hip
|
||||
let data = self.tiff.find_ifds_with_tag(TiffCommonTag::SubIFDs);
|
||||
if data.is_empty() {
|
||||
return Err(RawlerError::DecoderFailed("ARW: Couldn't find the data IFD!".to_string()));
|
||||
}
|
||||
let raw = data[0];
|
||||
let width = 3880;
|
||||
let height = 2608;
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::SubIFDs).force_usize(0);
|
||||
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
let image = ArwDecoder::decode_arw1(src, width, height, dummy);
|
||||
|
||||
// Get the WB the MRW way
|
||||
// DNGPrivateTag contains 4 bytes forming a LE u32 offset value.
|
||||
let priv_offset = {
|
||||
let entry = fetch_tiff_tag!(self.tiff, TiffCommonTag::DNGPrivateArea);
|
||||
assert_eq!(entry.value_type(), 0x1);
|
||||
LEu32(entry.get_data(), 0)
|
||||
};
|
||||
let buf = file.subview_until_eof(priv_offset as u64)?;
|
||||
if BEu32(buf, 0) != 0x4D5249 {
|
||||
// MRI
|
||||
return Err(format!("Invalid DNGPRIVATEDATA tag: 0x{:X}, expected 0x4D5249 ", BEu32(buf, 0)).into());
|
||||
}
|
||||
let mut currpos: usize = 8;
|
||||
let mut wb_coeffs: [f32; 4] = [1.0, 1.0, 1.0, f32::NAN];
|
||||
// At most we read 20 bytes from currpos so check we don't step outside that
|
||||
while currpos + 20 < buf.len() {
|
||||
let tag: u32 = BEu32(buf, currpos);
|
||||
let len: usize = LEu32(buf, currpos + 4) as usize;
|
||||
if tag == 0x574247 {
|
||||
// WBG
|
||||
wb_coeffs[0] = LEu16(buf, currpos + 12) as f32;
|
||||
wb_coeffs[1] = LEu16(buf, currpos + 14) as f32;
|
||||
wb_coeffs[2] = LEu16(buf, currpos + 14) as f32;
|
||||
wb_coeffs[3] = LEu16(buf, currpos + 18) as f32;
|
||||
break;
|
||||
}
|
||||
currpos += len + 8;
|
||||
}
|
||||
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, normalize_wb(wb_coeffs), image, dummy)
|
||||
}
|
||||
|
||||
fn image_srf(&self, file: &RawSource, dummy: bool) -> Result<RawImage> {
|
||||
let data = self.tiff.find_ifds_with_tag(TiffCommonTag::ImageWidth);
|
||||
if data.is_empty() {
|
||||
return Err(RawlerError::DecoderFailed("ARW: Couldn't find the data IFD!".to_string()));
|
||||
}
|
||||
let raw = data[0];
|
||||
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
|
||||
let image = if dummy {
|
||||
PixU16::new_uninit(width, height)
|
||||
} else {
|
||||
let buffer = file.as_vec()?;
|
||||
let len = width * height * 2;
|
||||
|
||||
// Constants taken from dcraw
|
||||
let off: usize = 862144;
|
||||
let key_off: usize = 200896;
|
||||
let head_off: usize = 164600;
|
||||
|
||||
// Replicate the dcraw contortions to get the "decryption" key
|
||||
let offset = (buffer[key_off] as usize) * 4;
|
||||
let first_key = BEu32(&buffer, key_off + offset);
|
||||
let head = ArwDecoder::sony_decrypt(&buffer, head_off, 40, first_key)?;
|
||||
let second_key = LEu32(&head, 22);
|
||||
|
||||
// "Decrypt" the whole image buffer
|
||||
let image_data = ArwDecoder::sony_decrypt(&buffer, off, len, second_key)?;
|
||||
decode_16be(&image_data, width, height, dummy)
|
||||
};
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, [f32::NAN, f32::NAN, f32::NAN, f32::NAN], image, dummy)
|
||||
}
|
||||
|
||||
pub(crate) fn decode_arw1(buf: &[u8], width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
let mut pump = BitPumpMSB::new(buf);
|
||||
|
||||
let mut sum: i32 = 0;
|
||||
for x in 0..width {
|
||||
let col = width - 1 - x;
|
||||
let mut row = 0;
|
||||
while row <= height {
|
||||
if row == height {
|
||||
row = 1;
|
||||
}
|
||||
|
||||
let mut len: u32 = 4 - pump.get_bits(2);
|
||||
if len == 3 && pump.get_bits(1) != 0 {
|
||||
len = 0;
|
||||
} else if len == 4 {
|
||||
let zeros = pump.peek_bits(13).leading_zeros() - 19;
|
||||
len += zeros;
|
||||
pump.get_bits(cmp::min(13, zeros + 1));
|
||||
}
|
||||
let diff: i32 = pump.get_ibits(len);
|
||||
sum += diff;
|
||||
if len > 0 && (diff & (1 << (len - 1))) == 0 {
|
||||
sum -= (1 << len) - 1;
|
||||
}
|
||||
out[row * width + col] = sum as u16;
|
||||
row += 2
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn decode_arw2(buf: &[u8], width: usize, height: usize, curve: &LookupTable, dummy: bool) -> PixU16 {
|
||||
decode_threaded(
|
||||
width,
|
||||
height,
|
||||
dummy,
|
||||
&(|out: &mut [u16], row| {
|
||||
let mut pump = BitPumpLSB::new(&buf[(row * width)..]);
|
||||
|
||||
let mut random = pump.peek_bits(16);
|
||||
for out in out.chunks_exact_mut(32) {
|
||||
// Process 32 pixels at a time in interleaved fashion
|
||||
for j in 0..2 {
|
||||
let max = pump.get_bits(11);
|
||||
let min = pump.get_bits(11);
|
||||
let delta = max - min;
|
||||
// Calculate the size of the data shift needed by how large the delta is
|
||||
// A delta with 11 bits requires a shift of 4, 10 bits of 3, etc
|
||||
let delta_shift: u32 = cmp::max(0, (32 - (delta.leading_zeros() as i32)) - 7) as u32;
|
||||
let imax = pump.get_bits(4) as usize;
|
||||
let imin = pump.get_bits(4) as usize;
|
||||
|
||||
for i in 0..16 {
|
||||
let val = if i == imax {
|
||||
max
|
||||
} else if i == imin {
|
||||
min
|
||||
} else {
|
||||
cmp::min(0x7ff, (pump.get_bits(7) << delta_shift) + min)
|
||||
};
|
||||
out[j + (i * 2)] = curve.dither((val << 1) as u16, &mut random);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Some newer cameras like Alpha-1 uses LJPEG compression, but in an awkward way.
|
||||
/// The image is split into 512x512 tiles with cpp = 1, but the LJPEG stream is
|
||||
/// compressed as 256x256 with cpp = 4. So the total of bytes matches, but the dimension
|
||||
/// is wrong. Actually, the LJPEG stream is two lines packed into a single line each
|
||||
/// decompressed line has the bayer pattern: RGGBRGGBRGGB...
|
||||
/// So we need to decompress first, then unpack the bayer pattern from one line
|
||||
/// into two lines.
|
||||
/// For resolution-reduced files (cpp=3), pixels are encoded in YCbCr color space.
|
||||
pub(crate) fn decode_ljpeg(camera: &Camera, file: &RawSource, raw: &IFD, dummy: bool) -> Result<PixU16> {
|
||||
let offsets = raw.get_entry(TiffCommonTag::TileOffsets).ok_or("Unable to find TileOffsets")?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let twidth = fetch_tiff_tag!(raw, TiffCommonTag::TileWidth).force_usize(0);
|
||||
let tlength = fetch_tiff_tag!(raw, TiffCommonTag::TileLength).force_usize(0);
|
||||
let cpp = fetch_tiff_tag!(raw, TiffCommonTag::SamplesPerPixel).force_usize(0);
|
||||
let coltiles = (width - 1) / twidth + 1;
|
||||
let rowtiles = (height - 1) / tlength + 1;
|
||||
|
||||
log::debug!("Sony ARW LJPEG raw: width: {}, height: {}, cpp: {}", width, height, cpp);
|
||||
log::debug!("LJPEG tile parameters: width: {}, length: {}, cpp: {}", twidth, tlength, cpp);
|
||||
|
||||
if coltiles * rowtiles != offsets.count() as usize {
|
||||
return Err(RawlerError::unsupported(
|
||||
camera,
|
||||
format!("ARW LJPEG: trying to decode {} tiles from {} offsets", coltiles * rowtiles, offsets.count()),
|
||||
));
|
||||
}
|
||||
let buffer = file.as_vec()?;
|
||||
|
||||
if cpp == 3 {
|
||||
let mut image = decode_threaded_multiline(
|
||||
width * cpp,
|
||||
height,
|
||||
tlength,
|
||||
dummy,
|
||||
&(|strip: &mut [u16], row| {
|
||||
let row = row / tlength;
|
||||
for col in 0..coltiles {
|
||||
log::debug!("Decode tile: row({}), col({})", row, col);
|
||||
let offset = offsets.force_usize(row * coltiles + col);
|
||||
let src = &buffer[offset..];
|
||||
let decompressor =
|
||||
LjpegDecompressor::new(src).map_err(|err| format!("Creating LJPEG decompressor for ARW LJPEG tile ({row},{col}) failed: {err}"))?;
|
||||
let cpp = 3;
|
||||
let w = 512;
|
||||
let h = 512;
|
||||
let mut data = vec![0; h * w * cpp];
|
||||
|
||||
decompressor.decode_sony(&mut data, 0, w * cpp, w * cpp, h, dummy)?;
|
||||
interpolate_yuv(decompressor.super_h(), decompressor.super_v(), w * cpp, h, &mut data);
|
||||
|
||||
let mut strip = &mut *strip;
|
||||
|
||||
for line in data.chunks_exact(w * cpp) {
|
||||
let base = col * twidth * cpp;
|
||||
strip[base..base + w * cpp].copy_from_slice(line);
|
||||
|
||||
// Now move output strip by one row.
|
||||
strip = &mut strip[width * cpp..];
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
)?;
|
||||
// Convert YC'bC'r data to RGB.
|
||||
ycbcr_to_rgb(&mut image.data);
|
||||
Ok(image)
|
||||
} else if cpp == 1 {
|
||||
decode_threaded_multiline(
|
||||
width,
|
||||
height,
|
||||
tlength,
|
||||
dummy,
|
||||
&(|strip: &mut [u16], row| {
|
||||
let row = row / tlength;
|
||||
for col in 0..coltiles {
|
||||
let offset = offsets.force_usize(row * coltiles + col);
|
||||
let src = &buffer[offset..];
|
||||
let decompressor = LjpegDecompressor::new(src)?;
|
||||
let cpp = 4;
|
||||
let w = 256;
|
||||
let h = 256;
|
||||
let mut data = vec![0; h * w * cpp];
|
||||
|
||||
decompressor.decode(&mut data, 0, w * cpp, w * cpp, h, dummy)?;
|
||||
|
||||
let mut strip = &mut *strip;
|
||||
for line in data.chunks_exact(1024) {
|
||||
for (i, chunk) in line.chunks_exact(4).enumerate() {
|
||||
// Unpack chunks of RGGB pixel data into two output lines
|
||||
// so the first line is RGRGRG and the second one is GBGBGB.
|
||||
strip[col * twidth + i * 2 + 0] = chunk[0];
|
||||
strip[col * twidth + i * 2 + 1] = chunk[1];
|
||||
strip[width + col * twidth + i * 2 + 0] = chunk[2];
|
||||
strip[width + col * twidth + i * 2 + 1] = chunk[3];
|
||||
}
|
||||
// Now move output strip by two rows.
|
||||
strip = &mut strip[width * 2..];
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
)
|
||||
.map_err(RawlerError::DecoderFailed)
|
||||
} else {
|
||||
Err(RawlerError::unsupported(
|
||||
camera,
|
||||
format!("NRW files with LJPEG compression and unsupported cpp: {}", cpp),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_params(&self, file: &RawSource) -> Result<ArwImageParams> {
|
||||
let priv_offset = {
|
||||
let tag = fetch_tiff_tag!(self.tiff, TiffCommonTag::DNGPrivateArea).get_data();
|
||||
LEu32(tag, 0)
|
||||
};
|
||||
let priv_tiff = IFD::new(&mut file.reader(), priv_offset, 0, 0, Endian::Little, &[])?;
|
||||
|
||||
//priv_tiff.dump::<ExifTag>(0).iter().for_each(|line| println!("DUMPXX: {}", line));
|
||||
|
||||
let sony_offset = fetch_tiff_tag!(priv_tiff, TiffCommonTag::SonyOffset).force_u32(0);
|
||||
let sony_length = fetch_tiff_tag!(priv_tiff, TiffCommonTag::SonyLength).force_usize(0);
|
||||
// This tag is of type UNDEFINED and contains a 32 bit value
|
||||
let sony_key = {
|
||||
let tag = fetch_tiff_tag!(priv_tiff, TiffCommonTag::SonyKey).get_data();
|
||||
LEu32(tag, 0)
|
||||
};
|
||||
let buffer = file.as_vec()?;
|
||||
let decrypted_buf = ArwDecoder::sony_decrypt(&buffer, sony_offset as usize, sony_length, sony_key)?;
|
||||
|
||||
let decrypted_tiff = IFD::new(&mut Cursor::new(decrypted_buf), 0, 0, -(sony_offset as i32), Endian::Little, &[])?;
|
||||
|
||||
let wb = self.get_wb(&decrypted_tiff)?;
|
||||
|
||||
let blacklevel = self.get_blacklevel(&decrypted_tiff);
|
||||
let whitelevel = self.get_whitelevel(&decrypted_tiff);
|
||||
|
||||
Ok(ArwImageParams { wb, blacklevel, whitelevel })
|
||||
}
|
||||
|
||||
fn get_blacklevel(&self, sr2: &IFD) -> Option<[u16; 4]> {
|
||||
if let Some(entry) = sr2.get_entry(SR2SubIFD::BlackLevel2) {
|
||||
if entry.count() == 4 {
|
||||
return Some([entry.force_u16(0), entry.force_u16(1), entry.force_u16(2), entry.force_u16(3)]);
|
||||
} else {
|
||||
return Some([entry.force_u16(0), entry.force_u16(0), entry.force_u16(0), entry.force_u16(0)]);
|
||||
}
|
||||
}
|
||||
if let Some(entry) = sr2.get_entry(SR2SubIFD::BlackLevel1) {
|
||||
if entry.count() == 4 {
|
||||
return Some([entry.force_u16(0), entry.force_u16(1), entry.force_u16(2), entry.force_u16(3)]);
|
||||
} else {
|
||||
return Some([entry.force_u16(0), entry.force_u16(0), entry.force_u16(0), entry.force_u16(0)]);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_whitelevel(&self, sr2: &IFD) -> Option<[u16; 4]> {
|
||||
if let Some(entry) = sr2.get_entry(SR2SubIFD::WhiteLevel) {
|
||||
if entry.count() == 4 {
|
||||
return Some([entry.force_u16(0), entry.force_u16(1), entry.force_u16(2), entry.force_u16(3)]);
|
||||
} else {
|
||||
return Some([entry.force_u16(0), entry.force_u16(0), entry.force_u16(0), entry.force_u16(0)]);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_wb(&self, sr2: &IFD) -> Result<[f32; 4]> {
|
||||
let grbg_levels = sr2.get_entry(SR2SubIFD::SonyGRBG);
|
||||
let rggb_levels = sr2.get_entry(SR2SubIFD::SonyRGGB);
|
||||
if let Some(levels) = grbg_levels {
|
||||
Ok(normalize_wb([
|
||||
levels.force_u32(1) as f32,
|
||||
levels.force_u32(0) as f32,
|
||||
levels.force_u32(3) as f32,
|
||||
levels.force_u32(2) as f32,
|
||||
]))
|
||||
} else if let Some(levels) = rggb_levels {
|
||||
Ok(normalize_wb([
|
||||
levels.force_u32(0) as f32,
|
||||
levels.force_u32(1) as f32,
|
||||
levels.force_u32(2) as f32,
|
||||
levels.force_u32(3) as f32,
|
||||
]))
|
||||
} else {
|
||||
Err(RawlerError::DecoderFailed("ARW: Couldn't find GRGB or RGGB levels".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_curve(raw: &IFD) -> Result<LookupTable> {
|
||||
let centry = fetch_tiff_tag!(raw, TiffCommonTag::SonyCurve);
|
||||
let mut curve: [usize; 6] = [0, 0, 0, 0, 0, 4095];
|
||||
|
||||
for i in 0..4 {
|
||||
curve[i + 1] = ((centry.force_u32(i) >> 2) & 0xfff) as usize;
|
||||
}
|
||||
|
||||
Ok(Self::calculate_curve(curve))
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_curve(curve: [usize; 6]) -> LookupTable {
|
||||
let mut out = vec![0_u16; curve[5] + 1];
|
||||
for i in 0..5 {
|
||||
for j in (curve[i] + 1)..(curve[i + 1] + 1) {
|
||||
out[j] = out[j - 1] + (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
LookupTable::new(&out)
|
||||
}
|
||||
|
||||
pub(crate) fn sony_decrypt(buf: &[u8], offset: usize, length: usize, key: u32) -> crate::Result<Vec<u8>> {
|
||||
if buf.len() < offset + 4 * (length / 4) {
|
||||
return Err(RawlerError::DecoderFailed("sony_decrypt() failed: buffer to short".into()));
|
||||
}
|
||||
let mut pad: [u32; 128] = [0_u32; 128];
|
||||
let mut mkey = key;
|
||||
// Initialize the decryption pad from the key
|
||||
for p in 0..4 {
|
||||
mkey = mkey.wrapping_mul(48828125).wrapping_add(1);
|
||||
pad[p] = mkey;
|
||||
}
|
||||
pad[3] = (pad[3] << 1) | ((pad[0] ^ pad[2]) >> 31);
|
||||
for p in 4..127 {
|
||||
pad[p] = ((pad[p - 4] ^ pad[p - 2]) << 1) | ((pad[p - 3] ^ pad[p - 1]) >> 31);
|
||||
}
|
||||
for p in 0..127 {
|
||||
pad[p] = u32::from_be(pad[p]);
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(length + 4);
|
||||
//for i in 0..(length / 4 + 1) {
|
||||
for i in 0..(length / 4) {
|
||||
let p = i + 127;
|
||||
pad[p & 127] = pad[(p + 1) & 127] ^ pad[(p + 1 + 64) & 127];
|
||||
let output = LEu32(buf, offset + i * 4) ^ pad[p & 127];
|
||||
out.push(((output >> 0) & 0xff) as u8);
|
||||
out.push(((output >> 8) & 0xff) as u8);
|
||||
out.push(((output >> 16) & 0xff) as u8);
|
||||
out.push(((output >> 24) & 0xff) as u8);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn get_raw_image_size(&self, raw_ifd: &IFD) -> Result<Option<Rect>> {
|
||||
if let Some(entry) = raw_ifd.get_entry(ExifTag::SonyRawImageSize) {
|
||||
Ok(Some(Rect::new(Point::default(), Dim2::new(entry.force_usize(0), entry.force_usize(1)))))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
debug!("ARW raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1]; // G1 should be 1024 and we use this as divisor
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(ArwMakernoteTag);
|
||||
|
||||
/// Specific Makernotes tags.
|
||||
/// These are only related to the Makernote IFD.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum ArwMakernoteTag {
|
||||
CameraInfo = 0x0010,
|
||||
Tag_940C = 0x940C,
|
||||
Tag_9050 = 0x9050,
|
||||
Tag_9405 = 0x9405,
|
||||
Tag_9416 = 0x9416, // replaces 0x9405 for the Sony ILCE-7SM3, from July 2020
|
||||
}
|
||||
|
||||
/// Decipher/encipher Sony tag 0x2010, 0x900b, 0x9050 and 0x940x data
|
||||
/// Extracted from exiftool, comment from PH:
|
||||
/// This is a simple substitution cipher, so use a hardcoded translation table for speed.
|
||||
/// The formula is: $c = ($b*$b*$b) % 249, where $c is the enciphered data byte
|
||||
/// note that bytes with values 249-255 are not translated, and 0-1, 82-84,
|
||||
/// 165-167 and 248 have the same enciphered value)
|
||||
const fn sony_tag9cxx_decipher_table() -> [u8; 256] {
|
||||
let mut tbl = [0; 256];
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if i >= 249 {
|
||||
tbl[i] = i as u8;
|
||||
} else {
|
||||
tbl[i * i * i % 249] = i as u8;
|
||||
}
|
||||
i += 1;
|
||||
if i >= tbl.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tbl
|
||||
}
|
||||
|
||||
const SONY_TAG_940X_DECIPHER_TABLE: [u8; 256] = sony_tag9cxx_decipher_table();
|
||||
|
||||
fn sony_tag9cxx_decipher(data: &[u8]) -> Vec<u8> {
|
||||
let mut buf = Vec::from(data);
|
||||
buf.iter_mut().for_each(|v| *v = SONY_TAG_940X_DECIPHER_TABLE[*v as usize]);
|
||||
buf
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ArwImageParams {
|
||||
wb: [f32; 4],
|
||||
blacklevel: Option<[u16; 4]>,
|
||||
whitelevel: Option<[u16; 4]>,
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(SR2SubIFD);
|
||||
|
||||
/// Specific Canon CR2 Makernotes tags.
|
||||
/// These are only related to the Makernote IFD.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SR2SubIFD {
|
||||
SonyGRBG = 0x7303,
|
||||
SonyRGGB = 0x7313,
|
||||
BlackLevel1 = 0x7300,
|
||||
BlackLevel2 = 0x7310,
|
||||
WhiteLevel = 0x787f,
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
use toml::Value;
|
||||
|
||||
use crate::CFA;
|
||||
use crate::cfa::PlaneColor;
|
||||
use crate::imgop::xyz::FlatColorMatrix;
|
||||
use crate::imgop::xyz::Illuminant;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::WhiteLevel;
|
||||
|
||||
/// Contains sanitized information about the raw image's properties
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Camera {
|
||||
pub make: String,
|
||||
pub model: String,
|
||||
pub mode: String,
|
||||
pub clean_make: String,
|
||||
pub clean_model: String,
|
||||
pub remark: Option<String>,
|
||||
pub filesize: usize,
|
||||
pub raw_width: usize,
|
||||
pub raw_height: usize,
|
||||
//pub orientation: Orientation,
|
||||
pub whitelevel: Option<Vec<u32>>,
|
||||
pub blacklevel: Option<Vec<u32>>,
|
||||
pub blackareah: Option<(usize, usize)>,
|
||||
pub blackareav: Option<(usize, usize)>,
|
||||
pub xyz_to_cam: [[f32; 3]; 4],
|
||||
pub color_matrix: HashMap<Illuminant, FlatColorMatrix>,
|
||||
pub cfa: CFA,
|
||||
pub plane_color: PlaneColor,
|
||||
// Active area relative to sensor size
|
||||
pub active_area: Option<[usize; 4]>,
|
||||
// Recommended area relative to sensor size
|
||||
pub crop_area: Option<[usize; 4]>,
|
||||
// Hint/Replacement for EXIF BITDEPTH info
|
||||
pub bps: Option<usize>,
|
||||
// The BPS of the output after decoding
|
||||
pub real_bps: usize,
|
||||
pub highres_width: usize,
|
||||
pub default_scale: DefaultScale,
|
||||
pub best_quality_scale: BestQualityScale,
|
||||
pub hints: Vec<String>,
|
||||
pub params: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct DefaultScale(pub [[u32; 2]; 2]);
|
||||
|
||||
impl Default for DefaultScale {
|
||||
fn default() -> Self {
|
||||
Self([[1, 1], [1, 1]])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct BestQualityScale(pub [u32; 2]);
|
||||
|
||||
impl Default for BestQualityScale {
|
||||
fn default() -> Self {
|
||||
Self([1, 1])
|
||||
}
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn find_hint(&self, hint: &str) -> bool {
|
||||
self.hints.contains(&hint.to_string())
|
||||
}
|
||||
|
||||
pub fn param_usize(&self, name: &str) -> Option<usize> {
|
||||
self.params.get(name).and_then(|p| p.as_integer()).map(|i| i as usize)
|
||||
}
|
||||
|
||||
pub fn param_i32(&self, name: &str) -> Option<i32> {
|
||||
self.params.get(name).and_then(|p| p.as_integer()).map(|i| i as i32)
|
||||
}
|
||||
|
||||
pub fn param_str(&self, name: &str) -> Option<&str> {
|
||||
self.params.get(name).and_then(|p| p.as_str())
|
||||
}
|
||||
|
||||
pub fn make_blacklevel(&self, cpp: usize) -> Option<BlackLevel> {
|
||||
self.blacklevel.as_ref().map(|x| {
|
||||
if x.len() == 1 {
|
||||
BlackLevel::new(&vec![x[0]; cpp], 1, 1, cpp)
|
||||
} else if x.len() == self.cfa.width * self.cfa.height * cpp {
|
||||
BlackLevel::new(x, self.cfa.width, self.cfa.height, cpp)
|
||||
} else {
|
||||
panic!("Invalid blacklevel data")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_whitelevel(&self, cpp: usize) -> Option<WhiteLevel> {
|
||||
self.whitelevel.as_ref().map(|x| {
|
||||
if x.len() == 1 {
|
||||
WhiteLevel(vec![x[0] as u32; cpp])
|
||||
} else if x.len() == cpp {
|
||||
WhiteLevel(x.clone())
|
||||
} else {
|
||||
panic!("Invalid whitelevel data")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_from_toml(&mut self, ct: &toml::value::Table) {
|
||||
for (name, val) in ct {
|
||||
match name.as_ref() {
|
||||
n @ "make" => {
|
||||
self.make = val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)).to_string();
|
||||
}
|
||||
n @ "model" => {
|
||||
self.model = val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)).to_string();
|
||||
}
|
||||
n @ "mode" => {
|
||||
self.mode = val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)).to_string();
|
||||
}
|
||||
n @ "clean_make" => {
|
||||
self.clean_make = val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)).to_string();
|
||||
}
|
||||
n @ "clean_model" => {
|
||||
self.clean_model = val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)).to_string();
|
||||
}
|
||||
n @ "remark" => {
|
||||
self.remark = Some(val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)).to_string());
|
||||
}
|
||||
n @ "whitepoint" => {
|
||||
let white = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as u32;
|
||||
self.whitelevel = Some(vec![white]);
|
||||
}
|
||||
n @ "blackpoint" => {
|
||||
let black = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as u32;
|
||||
self.blacklevel = Some(vec![black]);
|
||||
}
|
||||
n @ "blackareah" => {
|
||||
let vals = val.as_array().unwrap_or_else(|| panic!("{} must be an array", n));
|
||||
self.blackareah = Some((vals[0].as_integer().unwrap() as usize, vals[1].as_integer().unwrap() as usize));
|
||||
}
|
||||
n @ "blackareav" => {
|
||||
let vals = val.as_array().unwrap_or_else(|| panic!("{} must be an array", n));
|
||||
self.blackareav = Some((vals[0].as_integer().unwrap() as usize, vals[1].as_integer().unwrap() as usize));
|
||||
}
|
||||
"color_matrix" => {
|
||||
if let Some(color_matrix) = val.as_table() {
|
||||
for (illu_str, matrix) in color_matrix.into_iter() {
|
||||
let illu = Illuminant::new_from_str(illu_str).unwrap();
|
||||
let xyz_to_cam = matrix
|
||||
.as_array()
|
||||
.expect("color matrix must be array")
|
||||
.iter()
|
||||
.map(|a| a.as_float().expect("color matrix values must be float") as f32)
|
||||
.collect();
|
||||
self.color_matrix.insert(illu, xyz_to_cam);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid matrix spec for {}", self.clean_model);
|
||||
}
|
||||
assert!(!self.color_matrix.is_empty());
|
||||
}
|
||||
n @ "active_area" => {
|
||||
let crop_vals = val.as_array().unwrap_or_else(|| panic!("{} must be an array", n));
|
||||
let mut crop = [0, 0, 0, 0];
|
||||
for (i, val) in crop_vals.iter().enumerate() {
|
||||
crop[i] = val.as_integer().unwrap() as usize;
|
||||
}
|
||||
self.active_area = Some(crop);
|
||||
}
|
||||
n @ "crop_area" => {
|
||||
let crop_vals = val.as_array().unwrap_or_else(|| panic!("{} must be an array", n));
|
||||
let mut crop = [0, 0, 0, 0];
|
||||
for (i, val) in crop_vals.iter().enumerate() {
|
||||
crop[i] = val.as_integer().unwrap() as usize;
|
||||
}
|
||||
self.crop_area = Some(crop);
|
||||
}
|
||||
n @ "color_pattern" => {
|
||||
self.cfa = CFA::new(val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)));
|
||||
}
|
||||
n @ "plane_color" => {
|
||||
self.plane_color = PlaneColor::new(val.as_str().unwrap_or_else(|| panic!("{} must be a string", n)));
|
||||
}
|
||||
n @ "bps" => {
|
||||
self.bps = Some(val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as usize);
|
||||
}
|
||||
n @ "real_bps" => {
|
||||
self.real_bps = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as usize;
|
||||
}
|
||||
n @ "filesize" => {
|
||||
self.filesize = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as usize;
|
||||
}
|
||||
n @ "raw_width" => {
|
||||
self.raw_width = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as usize;
|
||||
}
|
||||
n @ "raw_height" => {
|
||||
self.raw_height = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as usize;
|
||||
}
|
||||
n @ "highres_width" => {
|
||||
self.highres_width = val.as_integer().unwrap_or_else(|| panic!("{} must be an integer", n)) as usize;
|
||||
}
|
||||
n @ "default_scale" => {
|
||||
let scale_vals = val.as_array().unwrap_or_else(|| panic!("{} must be an array", n));
|
||||
let scale_h = scale_vals[0].as_array().expect("must be array");
|
||||
let scale_v = scale_vals[1].as_array().expect("must be array");
|
||||
let scale = [
|
||||
[
|
||||
scale_h[0].as_integer().expect("must be integer") as u32,
|
||||
scale_h[1].as_integer().expect("must be integer") as u32,
|
||||
],
|
||||
[
|
||||
scale_v[0].as_integer().expect("must be integer") as u32,
|
||||
scale_v[1].as_integer().expect("must be integer") as u32,
|
||||
],
|
||||
];
|
||||
self.default_scale = DefaultScale(scale);
|
||||
}
|
||||
n @ "best_quality_scale" => {
|
||||
let scale_vals = val.as_array().unwrap_or_else(|| panic!("{} must be an array", n));
|
||||
self.best_quality_scale = BestQualityScale([
|
||||
scale_vals[0].as_integer().expect("must be integer") as u32,
|
||||
scale_vals[1].as_integer().expect("must be integer") as u32,
|
||||
]);
|
||||
}
|
||||
n @ "hints" => {
|
||||
self.hints = Vec::new();
|
||||
for hint in val.as_array().unwrap_or_else(|| panic!("{} must be an array", n)) {
|
||||
self.hints.push(hint.as_str().expect("hints must be a string").to_string());
|
||||
}
|
||||
}
|
||||
n @ "params" => {
|
||||
for (name, val) in val.as_table().unwrap_or_else(|| panic!("{} must be a table", n)) {
|
||||
self.params.insert(name.clone(), val.clone());
|
||||
}
|
||||
}
|
||||
"model_aliases" => {}
|
||||
"modes" => {} // ignore
|
||||
key => {
|
||||
panic!("Unknown key: {}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Camera {
|
||||
Camera {
|
||||
make: "".to_string(),
|
||||
model: "".to_string(),
|
||||
mode: "".to_string(),
|
||||
clean_make: "".to_string(),
|
||||
clean_model: "".to_string(),
|
||||
remark: None,
|
||||
filesize: 0,
|
||||
raw_width: 0,
|
||||
raw_height: 0,
|
||||
whitelevel: None,
|
||||
blacklevel: None,
|
||||
blackareah: None,
|
||||
blackareav: None,
|
||||
xyz_to_cam: [[0.0; 3]; 4],
|
||||
color_matrix: HashMap::new(),
|
||||
cfa: CFA::new(""),
|
||||
plane_color: PlaneColor::default(),
|
||||
active_area: None,
|
||||
crop_area: None,
|
||||
bps: None,
|
||||
real_bps: 16,
|
||||
highres_width: usize::MAX,
|
||||
default_scale: DefaultScale::default(),
|
||||
best_quality_scale: BestQualityScale::default(),
|
||||
hints: Vec::new(),
|
||||
params: HashMap::new(),
|
||||
//orientation: Orientation::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
+903
@@ -0,0 +1,903 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use core::panic;
|
||||
use image::DynamicImage;
|
||||
use image::ImageBuffer;
|
||||
use image::Rgb;
|
||||
use log::debug;
|
||||
use log::info;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rayon::slice::ParallelSliceMut;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::alloc_image_plain;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::LookupTable;
|
||||
use crate::bits::clampbits;
|
||||
use crate::decompressors::ljpeg::*;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Rational;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::Result;
|
||||
use super::WhiteLevel;
|
||||
|
||||
mod colordata;
|
||||
|
||||
pub(crate) use colordata::parse_colordata;
|
||||
|
||||
const CANON_EF_MOUNT: &str = "ef-mount";
|
||||
const CANON_CN_MOUNT: &str = "cn-mount";
|
||||
|
||||
/// CR2 Decoder
|
||||
pub struct Cr2Decoder<'a> {
|
||||
#[allow(dead_code)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
exif: IFD,
|
||||
makernote: Option<IFD>,
|
||||
#[allow(dead_code)]
|
||||
mode: Cr2Mode,
|
||||
xpacket: Option<Vec<u8>>,
|
||||
camera: Camera,
|
||||
model_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// CR2 format encapsulation for analyzer
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Cr2Format {
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl<'a> Decoder for Cr2Decoder<'a> {
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
FormatDump::Cr2(Cr2Format { tiff: self.tiff.clone() })
|
||||
}
|
||||
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
/*
|
||||
for (i, ifd) in self.tiff.chains().iter().enumerate() {
|
||||
eprintln!("IFD {}", i);
|
||||
for line in ifd_dump::<crate::tags::LegacyTiffRootTag>(ifd, 10) {
|
||||
eprintln!("{}", line);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
let camera = &self.camera;
|
||||
let (raw, offset) = {
|
||||
if let Some(raw) = self.tiff.find_first_ifd(TiffCommonTag::Cr2Id) {
|
||||
(raw, fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0))
|
||||
} else if let Some(raw) = self.tiff.find_first_ifd(TiffCommonTag::CFAPattern) {
|
||||
(raw, fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0))
|
||||
} else if let Some(off) = self.makernote.as_ref().and_then(|md| md.get_entry(TiffCommonTag::Cr2OldOffset)) {
|
||||
// Old Canon TIF files contains the offset in makernote tags
|
||||
(self.tiff.root_ifd(), off.value.force_usize(0))
|
||||
} else {
|
||||
return Err(RawlerError::DecoderFailed("CR2: Couldn't find raw info".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
// We don't have an excact length, so read until end.
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
|
||||
let (cpp, image) = {
|
||||
let decompressor = LjpegDecompressor::new(src)?;
|
||||
let ljpegwidth = decompressor.width();
|
||||
let mut width = ljpegwidth;
|
||||
let mut height = decompressor.height();
|
||||
let cpp = if decompressor.super_h() == 2 { 3 } else { 1 };
|
||||
debug!("CR2 ljpeg components: {}", decompressor.components());
|
||||
debug!("CR2 final cpp: {}", cpp);
|
||||
debug!("CR2 dimension: {},{}", width / cpp, height);
|
||||
let mut ljpegout = alloc_image_plain!(width, height, dummy);
|
||||
if !dummy {
|
||||
decompressor.decode(ljpegout.pixels_mut(), 0, width, width, height, dummy)?;
|
||||
}
|
||||
|
||||
//crate::devtools::dump_image_u16(&ljpegout, width, height, "/tmp/cr2_before_striped.pnm");
|
||||
|
||||
// Linearize the output (applies only to D2000 as far as I can tell)
|
||||
if !dummy && camera.find_hint("linearization") {
|
||||
let table = {
|
||||
let linearization = fetch_tiff_tag!(raw, TiffCommonTag::GrayResponse);
|
||||
let mut t = [0_u16; 4096];
|
||||
for i in 0..t.len() {
|
||||
t[i] = linearization.force_u16(i);
|
||||
}
|
||||
LookupTable::new(&t)
|
||||
};
|
||||
|
||||
let mut random = ljpegout[0] as u32;
|
||||
for p in ljpegout.pixels_mut().iter_mut() {
|
||||
*p = table.dither(*p, &mut random);
|
||||
}
|
||||
}
|
||||
|
||||
if cpp == 3 {
|
||||
if raw.has_entry(TiffCommonTag::ImageWidth) {
|
||||
width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0) * cpp;
|
||||
height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
} else if width / cpp < height {
|
||||
let temp = width / cpp;
|
||||
width = height * cpp;
|
||||
height = temp;
|
||||
}
|
||||
} else if camera.find_hint("double_line") {
|
||||
width /= 2;
|
||||
height *= 2;
|
||||
}
|
||||
|
||||
debug!("CR2 dimension2: {},{}", width / cpp, height);
|
||||
|
||||
// Take each of the vertical fields and put them into the right location
|
||||
// FIXME: Doing this at the decode would reduce about 5% in runtime but I haven't
|
||||
// been able to do it without hairy code
|
||||
if let Some(canoncol) = raw.get_entry(TiffCommonTag::Cr2StripeWidths) {
|
||||
debug!("Found Cr2StripeWidths tag: {:?}", canoncol.value);
|
||||
if canoncol.value.force_usize(0) == 0 {
|
||||
if cpp == 3 {
|
||||
self.convert_to_rgb(file, camera, &decompressor, width, height, ljpegout.pixels_mut(), dummy)?;
|
||||
//width /= 3;
|
||||
}
|
||||
ljpegout.update_dimension(Dim2::new(width, height));
|
||||
(cpp, ljpegout)
|
||||
/*
|
||||
if camera.find_hint("double_line") {
|
||||
(width, height, cpp, PixU16::new_with(ljpegout.into_inner(), width, height))
|
||||
} else {
|
||||
(width, height, cpp, ljpegout)
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
let mut out = alloc_image_plain!(width, height, dummy);
|
||||
if !dummy {
|
||||
let mut fieldwidths = Vec::new();
|
||||
debug_assert!(canoncol.value.force_usize(0) > 0);
|
||||
debug_assert!(canoncol.value.force_usize(1) > 0);
|
||||
debug_assert!(canoncol.value.force_usize(2) > 0);
|
||||
for _ in 0..canoncol.value.force_usize(0) {
|
||||
fieldwidths.push(canoncol.value.force_usize(1));
|
||||
}
|
||||
fieldwidths.push(canoncol.value.force_usize(2));
|
||||
|
||||
if decompressor.super_v() == 2 {
|
||||
debug!("CR2 v=2 decoder used, h={}", decompressor.super_h());
|
||||
// We've decoded 2 lines at a time so we also need to copy two strips at a time
|
||||
let nfields = fieldwidths.len();
|
||||
let fieldwidth = fieldwidths[0];
|
||||
let mut fieldstart = 0;
|
||||
let mut inpos = 0;
|
||||
for _ in 0..nfields {
|
||||
for row in (0..height).step_by(2) {
|
||||
for col in (0..fieldwidth).step_by(3) {
|
||||
let outpos = row * width + fieldstart + col;
|
||||
out[outpos..outpos + 3].copy_from_slice(&ljpegout[inpos..inpos + 3]);
|
||||
let outpos = (row + 1) * width + fieldstart + col;
|
||||
let inpos2 = inpos + ljpegwidth;
|
||||
out[outpos..outpos + 3].copy_from_slice(&ljpegout[inpos2..inpos2 + 3]);
|
||||
inpos += 3;
|
||||
if inpos % ljpegwidth == 0 {
|
||||
// we've used a full input line and we're reading 2 by 2 so skip one
|
||||
inpos += ljpegwidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
fieldstart += fieldwidth;
|
||||
}
|
||||
} else {
|
||||
let sh = decompressor.super_h();
|
||||
debug!("CR2 v=1 decoder used, super_h: {}", sh);
|
||||
let mut fieldstart = 0;
|
||||
let mut fieldpos = 0;
|
||||
for fieldwidth in fieldwidths {
|
||||
// fix the inconsistent slice width in sRaw mode, ask Canon.
|
||||
let fieldwidth = fieldwidth / sh * cpp;
|
||||
// The output for full height of a vertical stripe is
|
||||
// composed by the lines of all input stripes N:
|
||||
// outb(line0) = slice[0](line[0])
|
||||
// outb(line1) = slice[1](line[0])
|
||||
// outb(line2) = slice[N-1](line[0])
|
||||
for row in 0..height {
|
||||
let outpos = row * width + fieldstart;
|
||||
let inpos = fieldpos + row * fieldwidth;
|
||||
let outb = &mut out[outpos..outpos + fieldwidth];
|
||||
let inb = &ljpegout[inpos..inpos + fieldwidth];
|
||||
outb.copy_from_slice(inb);
|
||||
}
|
||||
fieldstart += fieldwidth;
|
||||
fieldpos += fieldwidth * height;
|
||||
}
|
||||
}
|
||||
}
|
||||
if cpp == 3 {
|
||||
self.convert_to_rgb(file, camera, &decompressor, width, height, out.pixels_mut(), dummy)?;
|
||||
//width /= 3;
|
||||
}
|
||||
(cpp, out)
|
||||
//(width, height, cpp, out)
|
||||
}
|
||||
} else {
|
||||
ljpegout.update_dimension(Dim2::new(width, height));
|
||||
(cpp, ljpegout)
|
||||
// (width, height, cpp, PixU16::new_with(ljpegout.into_inner(), width, height))
|
||||
}
|
||||
};
|
||||
|
||||
let wb = self.get_wb(file, camera)?;
|
||||
debug!("CR2 WB: {:?}", wb);
|
||||
//assert_eq!(image.width, width * cpp);
|
||||
|
||||
let blacklevel = self.get_blacklevel(camera, cpp)?;
|
||||
let whitelevel = self.get_whitelevel(cpp)?;
|
||||
|
||||
let photometric = match cpp {
|
||||
1 => RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera)),
|
||||
3 => RawPhotometricInterpretation::LinearRaw,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let mut img = RawImage::new(camera.clone(), image, cpp, wb, photometric, blacklevel, whitelevel, dummy);
|
||||
|
||||
if let Some(file_crop) = self.get_sensor_area()? {
|
||||
assert!(
|
||||
img.crop_area.is_none(),
|
||||
"Camera {} has embedded crop params, remove crop from config file!",
|
||||
self.camera.clean_make
|
||||
);
|
||||
img.crop_area = Some(file_crop);
|
||||
} else {
|
||||
//panic!("Camera {} has no embedded crops, but all CR2 should contain them?!", self.camera.clean_make);
|
||||
// 1D and D2000C has no crops!
|
||||
}
|
||||
|
||||
if cpp == 3 {
|
||||
// We have a sRAW or mRAW: the active_area from camera config is invalid now!
|
||||
// We just apply the crop_area that comes from metadata, which is correct.
|
||||
img.active_area = img.crop_area;
|
||||
}
|
||||
|
||||
debug!("Blacklevel: {:?}", img.blacklevel);
|
||||
debug!("Whitelevel: {:?}", img.whitelevel);
|
||||
debug!("Black areas: {:?}", img.blackareas);
|
||||
debug!("Active area: {:?}", img.active_area);
|
||||
debug!("Crop area: {:?}", img.crop_area);
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn xpacket(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.xpacket.clone())
|
||||
}
|
||||
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
// For CR2, there is a full resolution image in IFD0.
|
||||
// This is compressed with old-JPEG compression (Compression = 6)
|
||||
let root_ifd = &self.tiff.root_ifd();
|
||||
let buf = root_ifd
|
||||
.singlestrip_data_rawsource(file)
|
||||
.map_err(|e| RawlerError::DecoderFailed(format!("Failed to get strip data: {}", e)))?;
|
||||
let compression = root_ifd.get_entry(TiffCommonTag::Compression).ok_or("Missing tag")?.force_usize(0);
|
||||
let width = fetch_tiff_tag!(root_ifd, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(root_ifd, TiffCommonTag::ImageLength).force_usize(0);
|
||||
if compression == 1 {
|
||||
Ok(Some(DynamicImage::ImageRgb8(
|
||||
ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(width as u32, height as u32, buf.to_vec())
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to read uncompressed image")))?,
|
||||
)))
|
||||
} else {
|
||||
let img = image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to read JPEG image: {:?}", err)))?;
|
||||
Ok(Some(img))
|
||||
}
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::CR2
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum Cr2Mode {
|
||||
Raw,
|
||||
Sraw1,
|
||||
Sraw2,
|
||||
}
|
||||
|
||||
impl<'a> Cr2Decoder<'a> {
|
||||
fn get_mode(makernote: &Option<IFD>) -> Result<Cr2Mode> {
|
||||
if let Some(settings) = makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::CameraSettings)) {
|
||||
match settings.get_u16(46) {
|
||||
Ok(Some(0)) => Ok(Cr2Mode::Raw),
|
||||
Ok(Some(1)) => Ok(Cr2Mode::Sraw1),
|
||||
Ok(Some(2)) => Ok(Cr2Mode::Sraw2),
|
||||
Ok(None) => Ok(Cr2Mode::Raw),
|
||||
Ok(Some(v)) => Err(RawlerError::DecoderFailed(format!("Unknown sraw quality value found: {}", v))),
|
||||
Err(_) => Err(RawlerError::DecoderFailed("Unknown sraw quality value".to_string())),
|
||||
}
|
||||
} else {
|
||||
Ok(Cr2Mode::Raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new CR2 decoder
|
||||
/// This parses the RawFile again to include specific sub IFDs.
|
||||
pub fn new(file: &RawSource, _tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<Cr2Decoder<'a>> {
|
||||
debug!("CR2 decoder choosen");
|
||||
|
||||
// Parse the TIFF again, with custom settings
|
||||
let tiff = GenericTiffReader::new(&mut file.reader(), 0, 0, None, &[33424])?;
|
||||
|
||||
let exif = Self::new_exif_ifd(file, &tiff, rawloader)?;
|
||||
let makernote = Self::new_makernote(file, &tiff, &exif, rawloader)?;
|
||||
|
||||
let mode = Self::get_mode(&makernote)?;
|
||||
|
||||
debug!("sRaw quality: {:?}", mode);
|
||||
let mode_str = match mode {
|
||||
Cr2Mode::Raw => "",
|
||||
Cr2Mode::Sraw1 => "sRaw1",
|
||||
Cr2Mode::Sraw2 => "sRaw2",
|
||||
};
|
||||
|
||||
let camera = rawloader.check_supported_with_mode(tiff.root_ifd(), mode_str)?;
|
||||
|
||||
let xpacket = Self::read_xpacket(file, &tiff, rawloader)?;
|
||||
|
||||
let model_id = makernote
|
||||
.as_ref()
|
||||
.and_then(|mn| mn.get_entry(Cr2MakernoteTag::ModelId).and_then(|v| v.get_u32(0).transpose()))
|
||||
.transpose()
|
||||
.map_err(|_| RawlerError::DecoderFailed("CR2: invalid model id".to_string()))?;
|
||||
Ok(Cr2Decoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
exif,
|
||||
makernote,
|
||||
mode,
|
||||
xpacket,
|
||||
camera,
|
||||
model_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Search for EXIF IFD, if not found, fallback to root IFD.
|
||||
/// This is useful for EOS D2000 where EXIF tags are located in the root.
|
||||
fn new_exif_ifd(_file: &RawSource, tiff: &GenericTiffReader, _rawloader: &RawLoader) -> Result<IFD> {
|
||||
if let Some(exif_ifd) = tiff
|
||||
.root_ifd()
|
||||
.sub_ifds()
|
||||
.get(&TiffCommonTag::ExifIFDPointer.into())
|
||||
.and_then(|subs| subs.get(0))
|
||||
{
|
||||
Ok(exif_ifd.clone())
|
||||
} else {
|
||||
debug!("No EXIF IFD found, fallback to root IFD");
|
||||
Ok(tiff.root_ifd().clone())
|
||||
}
|
||||
/*
|
||||
if let Some(exif_ifd) = tiff.root_ifd().get_ifd(LegacyTiffRootTag::ExifIFDPointer, &mut file.reader())? {
|
||||
return Ok(exif_ifd);
|
||||
} else {
|
||||
return Ok(tiff.root_ifd().clone());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn get_focal_len(&self) -> Result<Option<Rational>> {
|
||||
if let Some(Entry {
|
||||
value: Value::Short(focal), ..
|
||||
}) = self.makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::FocalLen))
|
||||
{
|
||||
return Ok(focal.get(1).map(|v| Rational::new(*v as u32, 1)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
let exif_lens_name = if let Some(Entry {
|
||||
value: Value::Ascii(lens_id), ..
|
||||
}) = self.exif.get_entry(ExifTag::LensModel)
|
||||
{
|
||||
lens_id.strings().get(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match self.makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::CameraSettings)) {
|
||||
Some(Entry {
|
||||
value: Value::Short(settings), ..
|
||||
}) => {
|
||||
let lens_info = settings[22];
|
||||
debug!("Lens Info tag: {}", lens_info);
|
||||
let resolver = LensResolver::new()
|
||||
.with_lens_keyname(exif_lens_name)
|
||||
.with_camera(&self.camera) // must follow with_lens_keyname() as it my override key
|
||||
.with_lens_id((lens_info as u32, 0))
|
||||
.with_focal_len(self.get_focal_len()?)
|
||||
.with_mounts(&[CANON_CN_MOUNT.into(), CANON_EF_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Camera settings in makernote not found, no lens data available");
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Parse the Canon makernote IFD
|
||||
fn new_makernote(file: &RawSource, tiff: &GenericTiffReader, exif_ifd: &IFD, _rawloader: &RawLoader) -> Result<Option<IFD>> {
|
||||
if let Some(entry) = exif_ifd.get_entry(TiffCommonTag::Makernote) {
|
||||
let offset = entry.offset().expect("Makernote internal offset is not present but should be");
|
||||
let makernote = tiff.parse_ifd(&mut file.reader(), offset as u32, 0, 0, exif_ifd.endian, &[])?;
|
||||
return Ok(Some(makernote));
|
||||
}
|
||||
info!("No makernote tag found");
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Read XMP data from TIFF entry
|
||||
/// This is useful as it stores the image rating (if present).
|
||||
fn read_xpacket(_file: &RawSource, tiff: &GenericTiffReader, _rawloader: &RawLoader) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(entry) = tiff.root_ifd().get_entry(TiffCommonTag::Xmp) {
|
||||
if let Entry { value: Value::Byte(xmp), .. } = entry {
|
||||
Ok(Some(xmp.clone()))
|
||||
} else {
|
||||
Err("Image has XMP data but invalid tag type!".into())
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn new_makernote(buf: &'a[u8], offset: usize, base_offset: usize, chain_level: isize, e: Endian) -> Result<LegacyTiffIFD<'a>> {
|
||||
let mut off = 0;
|
||||
let data = &buf[offset..];
|
||||
let mut endian = e;
|
||||
|
||||
// Some have MM or II to indicate endianness - read that
|
||||
if data[off..off+2] == b"II"[..] {
|
||||
off +=2;
|
||||
endian = Endian::Little;
|
||||
} if data[off..off+2] == b"MM"[..] {
|
||||
off +=2;
|
||||
endian = Endian::Big;
|
||||
}
|
||||
|
||||
Ok(LegacyTiffIFD::new(buf, offset+off, base_offset, 0, chain_level+1, endian, &vec![])?)
|
||||
}
|
||||
*/
|
||||
|
||||
/// Build firmware value from string
|
||||
fn get_firmware(&self) -> Result<Option<u32>> {
|
||||
Ok(
|
||||
match self
|
||||
.makernote
|
||||
.as_ref()
|
||||
.and_then(|mn| mn.get_entry(Cr2MakernoteTag::FirmareVer).and_then(|v| v.as_string()))
|
||||
{
|
||||
Some(fw) => {
|
||||
let str: String = fw.chars().filter(|c| c.is_ascii_digit() || c == &'.').collect();
|
||||
let v: Vec<u8> = str.split('.').map(|v| v.parse().expect("Only digits here")).collect();
|
||||
Some(v.iter().rev().enumerate().map(|(i, v)| 10_u32.pow(i as u32 * 3) * *v as u32).sum())
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the SRAW white balance coefficents from COLORDATA tag
|
||||
/// The offsets are always at offset 78.
|
||||
/// These coefficents are used for SRAW YUV2RGB conversion.
|
||||
fn get_sraw_wb(&self, rawfile: &RawSource, _cam: &Camera) -> Result<[f32; 4]> {
|
||||
if let Some(levels) = self
|
||||
.makernote
|
||||
.as_ref()
|
||||
.and_then(|mn| mn.get_entry_raw(Cr2MakernoteTag::ColorData, &mut rawfile.reader()).transpose())
|
||||
.transpose()?
|
||||
{
|
||||
let offset = 78;
|
||||
return Ok([
|
||||
levels.get_force_u16(offset) as f32,
|
||||
(levels.get_force_u16(offset + 1) as f32 + levels.get_force_u16(offset + 2) as f32) / 2.0,
|
||||
levels.get_force_u16(offset + 3) as f32,
|
||||
f32::NAN,
|
||||
]);
|
||||
}
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
|
||||
/// Get the white balance coefficents from COLORDATA tag
|
||||
/// The offsets are different, so we take the offset from camera params.
|
||||
fn get_wb(&self, rawfile: &RawSource, _cam: &Camera) -> Result<[f32; 4]> {
|
||||
if let Some(colordata) = self.makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::ColorData)) {
|
||||
let raw_wb = colordata::parse_colordata(colordata)?.wb;
|
||||
return Ok(normalize_wb(raw_wb));
|
||||
}
|
||||
|
||||
// TODO: check if these tags belongs to RootIFD or makernote
|
||||
if let Some(levels) = self.tiff.get_entry_raw(TiffCommonTag::Cr2PowerShotWB, &mut rawfile.reader())? {
|
||||
Ok([
|
||||
levels.get_force_u32(3) as f32,
|
||||
levels.get_force_u32(2) as f32,
|
||||
levels.get_force_u32(4) as f32,
|
||||
f32::NAN,
|
||||
])
|
||||
} else if let Some(levels) = self.tiff.get_entry(TiffCommonTag::Cr2OldWB) {
|
||||
Ok([levels.force_f32(0), levels.force_f32(1), levels.force_f32(2), f32::NAN])
|
||||
} else {
|
||||
// At least the D2000 has no WB
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the black level from COLORDATA tag
|
||||
/// The offsets are different, so we take the offset from camera params.
|
||||
fn get_blacklevel(&self, cam: &Camera, cpp: usize) -> Result<Option<BlackLevel>> {
|
||||
if let Some(colordata) = self.makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::ColorData)) {
|
||||
if let Some(blacklevel) = colordata::parse_colordata(colordata)?.blacklevel {
|
||||
match cpp {
|
||||
1 => return Ok(Some(BlackLevel::new(&blacklevel, cam.cfa.width, cam.cfa.height, cpp))),
|
||||
3 => {
|
||||
let avg = blacklevel.into_iter().sum::<u16>() / 4;
|
||||
let levels: [u16; 3] = [avg, avg, avg];
|
||||
return Ok(Some(BlackLevel::new(&levels, 1, 1, cpp)));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get the white level from COLORDATA tag
|
||||
/// The offsets are different, so we take the offset from camera params.
|
||||
fn get_whitelevel(&self, cpp: usize) -> Result<Option<WhiteLevel>> {
|
||||
if let Some(colordata) = self.makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::ColorData)) {
|
||||
if let Some(whitelevel) = colordata::parse_colordata(colordata)?.specular_whitelevel {
|
||||
return Ok(Some(WhiteLevel(vec![whitelevel as u32; cpp])));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get the SENSOR information, if available
|
||||
/// If not, fall back to sensor dimension reported by width/hight values.
|
||||
fn get_sensor_area(&self) -> Result<Option<Rect>> {
|
||||
if let Some(sensorinfo) = self.makernote.as_ref().and_then(|mn| mn.get_entry(Cr2MakernoteTag::SensorInfo)) {
|
||||
match &sensorinfo.value {
|
||||
Value::Short(v) => {
|
||||
debug!("Sensor info: {:?}", v);
|
||||
let _w = v[1] as usize;
|
||||
let _h = v[2] as usize;
|
||||
let left = v[5] as usize;
|
||||
let top = v[6] as usize;
|
||||
let right = v[7] as usize;
|
||||
let bottom = v[8] as usize;
|
||||
Ok(Some(Rect::new_with_points(Point::new(left, top), Point::new(right + 1, bottom + 1))))
|
||||
}
|
||||
_ => Err(RawlerError::DecoderFailed("Makernote contains invalid type for SensorInfo tag".to_string())),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpolate YCbCr (YUV) data
|
||||
fn interpolate_yuv(&self, ljpeg: &LjpegDecompressor, width: usize, _height: usize, image: &mut [u16]) {
|
||||
if ljpeg.super_h() == 1 && ljpeg.super_v() == 1 {
|
||||
return; // No interpolation needed
|
||||
}
|
||||
// Iterate over a block of 3 rows, smaller chunks are okay
|
||||
// but must always a multiple of row width.
|
||||
image.par_chunks_mut(width * 3).for_each(|slice| {
|
||||
// Do horizontal interpolation.
|
||||
// [y1 Cb Cr ] [ y2 . . ] [y1 Cb Cr ] [ y2 . . ] ...
|
||||
if ljpeg.super_h() == 2 {
|
||||
debug_assert_eq!(slice.len() % width, 0);
|
||||
for row in 0..(slice.len() / width) {
|
||||
for col in (6..width).step_by(6) {
|
||||
let pix1 = row * width + col - 6;
|
||||
let pix2 = pix1 + 3;
|
||||
let pix3 = row * width + col;
|
||||
slice[pix2 + 1] = ((slice[pix1 + 1] as i32 + slice[pix3 + 1] as i32 + 1) / 2) as u16;
|
||||
slice[pix2 + 2] = ((slice[pix1 + 2] as i32 + slice[pix3 + 2] as i32 + 1) / 2) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do vertical interpolation
|
||||
// pixel n pixel n+1 pixel n+2 pixel n+3 ...
|
||||
// row i : [y1 Cb Cr ] [ y2 Cb* Cr* ] [y1 Cb Cr ] [ y2 Cb* Cr* ] ...
|
||||
// row i+1: [y3 Cb* Cr*] [ y4 Cb** Cr**] [y3 Cb* Cr*] [ y4 Cb** Cr**] ...
|
||||
// row i+2: [y1 Cb Cr ] [ y2 Cb* Cr* ] [y1 Cb Cr ] [ y2 Cb* Cr* ] ...
|
||||
// row i+3: [y3 Cb* Cr*] [ y4 Cb** Cr**] [y3 Cb* Cr*] [ y4 Cb** Cr**] ...
|
||||
if ljpeg.super_v() == 2 && slice.len() == width * 3 {
|
||||
for col in (0..width).step_by(3) {
|
||||
let pix1 = col;
|
||||
let pix2 = width + col;
|
||||
let pix3 = 2 * width + col;
|
||||
slice[pix2 + 1] = ((slice[pix1 + 1] as i32 + slice[pix3 + 1] as i32 + 1) / 2) as u16;
|
||||
slice[pix2 + 2] = ((slice[pix1 + 2] as i32 + slice[pix3 + 2] as i32 + 1) / 2) as u16;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Old non-parallel code
|
||||
if ljpeg.super_h() == 2 {
|
||||
for row in 0..height {
|
||||
for col in (6..width).step_by(6) {
|
||||
let pix1 = row * width + col - 6;
|
||||
let pix2 = pix1 + 3;
|
||||
let pix3 = row * width + col;
|
||||
image[pix2 + 1] = ((image[pix1 + 1] as i32 + image[pix3 + 1] as i32 + 1) / 2) as u16;
|
||||
image[pix2 + 2] = ((image[pix1 + 2] as i32 + image[pix3 + 2] as i32 + 1) / 2) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ljpeg.super_v() == 2 {
|
||||
for row in (1..height - 1).step_by(2) {
|
||||
for col in (0..width).step_by(3) {
|
||||
let pix1 = (row - 1) * width + col;
|
||||
let pix2 = row * width + col;
|
||||
let pix3 = (row + 1) * width + col;
|
||||
image[pix2 + 1] = ((image[pix1 + 1] as i32 + image[pix3 + 1] as i32 + 1) / 2) as u16;
|
||||
image[pix2 + 2] = ((image[pix1 + 2] as i32 + image[pix3 + 2] as i32 + 1) / 2) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Convert YCbCr (YUV) data to linear RGB
|
||||
fn convert_to_rgb(
|
||||
&self,
|
||||
rawfile: &RawSource,
|
||||
cam: &Camera,
|
||||
ljpeg: &LjpegDecompressor,
|
||||
width: usize,
|
||||
height: usize,
|
||||
image: &mut [u16],
|
||||
dummy: bool,
|
||||
) -> Result<()> {
|
||||
debug!("YUV2RGB: Regular WB: {:?}", self.get_wb(rawfile, cam));
|
||||
debug!("YUV2RGB: SRAW WB: {:?}", self.get_sraw_wb(rawfile, cam));
|
||||
debug!("Model ID: 0x{:X}", self.model_id.unwrap_or(0));
|
||||
if dummy {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let do_interpolate = std::env::var("RAWLER_CR2_YUV_INTERPOLATE")
|
||||
.ok()
|
||||
.map(|id| id.parse::<bool>().expect("RAWLER_CR2_YUV_INTERPOLATE must by of type bool"))
|
||||
.unwrap_or(true);
|
||||
if do_interpolate {
|
||||
self.interpolate_yuv(ljpeg, width, height, image);
|
||||
}
|
||||
|
||||
let coeffs = self.get_sraw_wb(rawfile, cam)?;
|
||||
let (c1, c2, c3) = if cam.find_hint("invert_sraw_wb") {
|
||||
let c1 = (1024.0 * 1024.0 / coeffs[0]) as i32;
|
||||
let c2 = coeffs[1] as i32;
|
||||
let c3 = (1024.0 * 1024.0 / coeffs[2]) as i32;
|
||||
(c1, c2, c3)
|
||||
} else {
|
||||
(coeffs[0] as i32, coeffs[1] as i32, coeffs[2] as i32)
|
||||
};
|
||||
|
||||
// Starting with 40D, sRaw format was introduced. This uses
|
||||
// version 0. With 5D Mark II, version 1 gets used.
|
||||
// And with 5D Mark III, back to version 0 method
|
||||
// but without an offset of 512 for y.
|
||||
let version = if cam.find_hint("sraw_40d") {
|
||||
0
|
||||
} else if cam.find_hint("sraw_new") {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let fw = self.get_firmware()?;
|
||||
debug!("Firmware: {:?}", fw);
|
||||
|
||||
// This magic comes from dcraw.
|
||||
// Seems to because of rounding during interpolation, we need to
|
||||
// adjust the hue a little bit (only guessing)
|
||||
let hue = match self.model_id {
|
||||
None => 0,
|
||||
Some(model_id) => {
|
||||
if model_id >= 0x80000281 || (model_id == 0x80000218 && fw.unwrap_or(0) > 1000006) {
|
||||
(((ljpeg.super_h() * ljpeg.super_v()) - 1) >> 1) as i32
|
||||
} else {
|
||||
(ljpeg.super_h() * ljpeg.super_v()) as i32
|
||||
}
|
||||
}
|
||||
};
|
||||
debug!("SRAW hue correction: {:?}", hue);
|
||||
|
||||
// Now calculate RGB for each YUV tuple.
|
||||
image.par_chunks_exact_mut(3).for_each(|pix| {
|
||||
let y = pix[0] as i32;
|
||||
let cb = pix[1] as i32 - 16383;
|
||||
let cr = pix[2] as i32 - 16383;
|
||||
match version {
|
||||
0 => {
|
||||
let y = y - 512; // correction for 40D and others
|
||||
let r = c1 * (y + cr);
|
||||
let g = c2 * (y + ((-778 * cb - (cr << 11)) >> 12));
|
||||
let b = c3 * (y + cb);
|
||||
pix[0] = clampbits(r >> 8, 16);
|
||||
pix[1] = clampbits(g >> 8, 16);
|
||||
pix[2] = clampbits(b >> 8, 16);
|
||||
}
|
||||
1 => {
|
||||
// found in EOS 5D Mark II
|
||||
let cb = (cb << 2) + hue;
|
||||
let cr = (cr << 2) + hue;
|
||||
let r = c1 * (y + ((50 * cb + 22929 * cr) >> 14));
|
||||
let g = c2 * (y + ((-5640 * cb - 11751 * cr) >> 14));
|
||||
let b = c3 * (y + ((29040 * cb - 101 * cr) >> 14));
|
||||
pix[0] = clampbits(r >> 8, 16);
|
||||
pix[1] = clampbits(g >> 8, 16);
|
||||
pix[2] = clampbits(b >> 8, 16);
|
||||
}
|
||||
2 => {
|
||||
// found in EOS 5D Mark III and others
|
||||
let r = c1 * (y + cr);
|
||||
let g = c2 * (y + ((-778 * cb - (cr << 11)) >> 12));
|
||||
let b = c3 * (y + cb);
|
||||
pix[0] = clampbits(r >> 8, 16);
|
||||
pix[1] = clampbits(g >> 8, 16);
|
||||
pix[2] = clampbits(b >> 8, 16);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
debug!("CR2 raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1]; // G1 should be 1024 and we use this as divisor
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(Cr2MakernoteTag);
|
||||
|
||||
/// Specific Canon CR2 Makernotes tags.
|
||||
/// These are only related to the Makernote IFD.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum Cr2MakernoteTag {
|
||||
CameraSettings = 0x0001,
|
||||
FocalLen = 0x0002,
|
||||
FlashInfo = 0x0003,
|
||||
ShotInfo = 0x0004,
|
||||
Panorama = 0x0005,
|
||||
ImageType = 0x0006,
|
||||
FirmareVer = 0x0007,
|
||||
FileNumber = 0x0008,
|
||||
OwnerName = 0x0009,
|
||||
UnknownD30 = 0x000a,
|
||||
SerialNum = 0x000c,
|
||||
CameraInfo = 0x000d,
|
||||
FileLen = 0x000e,
|
||||
CustomFunc = 0x000f,
|
||||
ModelId = 0x0010,
|
||||
MovieInfo = 0x0011,
|
||||
AFInfo = 0x0012,
|
||||
ThumbArea = 0x0013,
|
||||
SerialFormat = 0x0014,
|
||||
SuperMacro = 0x001a,
|
||||
DateStampMode = 0x001c,
|
||||
MyColors = 0x001d,
|
||||
FirmwareRev = 0x001e,
|
||||
Categories = 0x0023,
|
||||
FaceDetect1 = 0x0024,
|
||||
FaceDetect2 = 0x0025,
|
||||
AFInfo2 = 0x0026,
|
||||
ContrastInfo = 0x0027,
|
||||
ImgUniqueID = 0x0028,
|
||||
WBInfo = 0x0029,
|
||||
FaceDetect3 = 0x002f,
|
||||
TimeInfo = 0x0035,
|
||||
BatteryType = 0x0038,
|
||||
AFInfo3 = 0x003c,
|
||||
RawDataOffset = 0x0081,
|
||||
OrigDecisionDataOffset = 0x0083,
|
||||
CustomFunc1D = 0x0090,
|
||||
PersFunc = 0x0091,
|
||||
PersFuncValues = 0x0092,
|
||||
FileInfo = 0x0093,
|
||||
AFPointsInFocus1D = 0x0094,
|
||||
LensModel = 0x0095,
|
||||
InternalSerial = 0x0096,
|
||||
DustRemovalData = 0x0097,
|
||||
CropInfo = 0x0098,
|
||||
CustomFunc2 = 0x0099,
|
||||
AspectInfo = 0x009a,
|
||||
ProcessingInfo = 0x00a0,
|
||||
ToneCurveTable = 0x00a1,
|
||||
SharpnessTable = 0x00a2,
|
||||
SharpnessFreqTable = 0x00a3,
|
||||
WhiteBalanceTable = 0x00a4,
|
||||
ColorBalance = 0x00a9,
|
||||
MeasuredColor = 0x00aa,
|
||||
ColorTemp = 0x00ae,
|
||||
CanonFlags = 0x00b0,
|
||||
ModifiedInfo = 0x00b1,
|
||||
TnoeCurveMatching = 0x00b2,
|
||||
WhiteBalanceMatching = 0x00b3,
|
||||
ColorSpace = 0x00b4,
|
||||
PreviewImageInfo = 0x00b6,
|
||||
VRDOffset = 0x00d0,
|
||||
SensorInfo = 0x00e0,
|
||||
ColorData = 0x4001,
|
||||
CRWParam = 0x4002,
|
||||
ColorInfo = 0x4003,
|
||||
Flavor = 0x4005,
|
||||
PictureStyleUserDef = 0x4008,
|
||||
PictureStylePC = 0x4009,
|
||||
CustomPictureStyleFileName = 0x4010,
|
||||
AFMicroAdj = 0x4013,
|
||||
VignettingCorr = 0x4015,
|
||||
VignettingCorr2 = 0x4016,
|
||||
LightningOpt = 0x4018,
|
||||
LensInfo = 0x4019,
|
||||
AmbienceInfo = 0x4020,
|
||||
MultiExp = 0x4021,
|
||||
FilterInfo = 0x4024,
|
||||
HDRInfo = 0x4025,
|
||||
AFConfig = 0x4028,
|
||||
RawBurstModeRoll = 0x403f,
|
||||
}
|
||||
|
||||
//const CR2_MODEL_40D: u32 = 0x80000190;
|
||||
@@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
RawlerError, Result,
|
||||
formats::tiff::{Entry, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct ColorData {
|
||||
pub(crate) version: i16,
|
||||
pub(crate) wb: [f32; 4],
|
||||
pub(crate) blacklevel: Option<[u16; 4]>,
|
||||
pub(crate) normal_whitelevel: Option<u16>,
|
||||
pub(crate) specular_whitelevel: Option<u16>,
|
||||
}
|
||||
|
||||
impl ColorData {
|
||||
fn new(data: &[u16], version: i16, wb_off: usize, black_off: Option<usize>, norm_white_off: Option<usize>, specular_white_off: Option<usize>) -> Self {
|
||||
log::debug!("Found Canon COLORDATA version: {}, data len: {}", version, data.len());
|
||||
let wb = [data[wb_off] as f32, data[wb_off + 1] as f32, data[wb_off + 2] as f32, data[wb_off + 3] as f32];
|
||||
let blacklevel = black_off.map(|off| [data[off], data[off + 1], data[off + 2], data[off + 3]]);
|
||||
let normal_whitelevel = norm_white_off.map(|off| data[off]);
|
||||
let specular_whitelevel = specular_white_off.map(|off| data[off]);
|
||||
|
||||
Self {
|
||||
version,
|
||||
wb,
|
||||
blacklevel,
|
||||
normal_whitelevel,
|
||||
specular_whitelevel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_colordata(colordata: &Entry) -> Result<ColorData> {
|
||||
match &colordata.value {
|
||||
Value::Undefined(undef) => {
|
||||
let transmuted: Vec<u16> = undef.chunks(2).map(|v| ((v[1] as u16) << 8) | (v[0] as u16)).collect();
|
||||
let data = &transmuted;
|
||||
let version: i16 = data[0] as i16;
|
||||
|
||||
Ok(match version {
|
||||
// -4 (M100/M5/M6)
|
||||
-4 => ColorData::new(data, version, 0x47, Some(0x14d), Some(0x0569), Some(0x056a)),
|
||||
// -3 (M10/M3)
|
||||
-3 => ColorData::new(data, version, 0x47, Some(0x108), None, None),
|
||||
|
||||
_ => return Err(format!("Unknown2 COLORDATA version: {}", data[0]).into()),
|
||||
})
|
||||
|
||||
/*
|
||||
Ok(match data.len() {
|
||||
// 20D and 350D
|
||||
582 => ColorData::new(&data, version, 0x19, None, None, None),
|
||||
// 1DmkII and 1DSmkII
|
||||
653 => ColorData::new(&data, version, 0x22, None, None, None),
|
||||
// 1DmkIIN, 5D, 30D, 400D
|
||||
796 => ColorData::new(&data, version, 0x3f, Some(0xc4), None, None),
|
||||
_ => panic!("COLORDATA count of {} is unknown", data.len())
|
||||
})
|
||||
*/
|
||||
}
|
||||
Value::Short(data) => {
|
||||
match data.len() {
|
||||
// 20D and 350D
|
||||
582 => return Ok(ColorData::new(data, 0, 0x19, None, None, None)),
|
||||
// 1DmkII and 1DSmkII
|
||||
653 => return Ok(ColorData::new(data, 0, 0x22, None, None, None)),
|
||||
// 1DmkIIN, 5D, 30D, 400D
|
||||
796 => return Ok(ColorData::new(data, 0, 0x3f, Some(0xc4), None, None)),
|
||||
_ => log::debug!("COLORDATA count of {} is unknown, continue with version matching", data.len()),
|
||||
}
|
||||
|
||||
let version: i16 = data[0] as i16;
|
||||
/*
|
||||
for (i, item) in data.iter().enumerate() {
|
||||
if *item > 127 {
|
||||
eprintln!("0x{:>4x}: {}", i, item);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Ok(match version {
|
||||
// 1 = (1DmkIIN/5D/30D/400D)
|
||||
1 => ColorData::new(data, version, 63, Some(196), None, None),
|
||||
// 2 (1DmkIII)
|
||||
// 3 (40D)
|
||||
2 | 3 => ColorData::new(data, version, 0x3f, Some(0xe7), None, None),
|
||||
// 4 (1DSmkIII)
|
||||
// 5 (450D/1000D)
|
||||
4 | 5 => ColorData::new(data, version, 0x3f, Some(0x2b4), Some(0x2b8), Some(0x2b9)),
|
||||
// 6 (50D/5DmkII)
|
||||
// 7 (500D/550D/7D/1DmkIV)
|
||||
6 | 7 => ColorData::new(data, version, 0x3f, Some(0x2cb), Some(0x2cf), Some(0x2d0)),
|
||||
// 9 (60D/1100D)
|
||||
9 => ColorData::new(data, version, 0x3f, Some(0x2cf), Some(0x2d3), Some(0x2d4)),
|
||||
// -4 (M100/M5/M6)
|
||||
-4 => ColorData::new(data, version, 0x47, Some(0x14d), Some(0x0569), Some(0x056a)),
|
||||
// -3 (M10/M3)
|
||||
-3 => ColorData::new(data, version, 0x47, Some(0x108), None, None),
|
||||
|
||||
// 10 (600D/1200D)
|
||||
// 10 (1DX/5DmkIII/6D/70D/100D/650D/700D/M/M2)
|
||||
10 => {
|
||||
if data.len() == 1273 || data.len() == 1275 {
|
||||
ColorData::new(data, version, 0x3f, Some(0x1df), Some(0x1e3), Some(0x1e4))
|
||||
} else {
|
||||
ColorData::new(data, version, 0x3f, Some(0x1f8), Some(0x1fc), Some(0x1fd))
|
||||
}
|
||||
}
|
||||
// 11 (7DmkII/750D/760D/8000D)
|
||||
11 => ColorData::new(data, version, 0x3f, Some(0x2d8), Some(0x2dc), Some(0x2dd)),
|
||||
|
||||
// 12 (1DXmkII/5DS/5DSR)
|
||||
12 => ColorData::new(data, version, 0x3f, Some(0x30a), Some(0x30e), Some(0x30f)),
|
||||
// 13 (80D/5DmkIV)
|
||||
13 => ColorData::new(data, version, 0x3f, Some(0x30a), Some(0x30e), Some(0x30f)),
|
||||
// 14 (1300D/2000D/4000D)
|
||||
14 => ColorData::new(data, version, 0x3f, Some(0x22c), Some(0x230), Some(0x231)),
|
||||
// 15 (6DmkII/77D/200D/800D,9000D)
|
||||
15 => ColorData::new(data, version, 0x3f, Some(0x30a), Some(0x30e), Some(0x30f)),
|
||||
// 16 (M50)
|
||||
// 17 (EOS R)
|
||||
// 18 (EOS RP/250D)
|
||||
// 19 (90D/850D/M6mkII/M200)
|
||||
16 | 17 | 18 | 19 => ColorData::new(data, version, 0x47, Some(0x149), Some(0x31c), Some(0x31d)),
|
||||
// 32 (1DXmkIII)
|
||||
// 33 (R5/R6)
|
||||
32 | 33 => ColorData::new(data, version, 0x55, Some(0x157), Some(0x32a), Some(0x32b)),
|
||||
// 34 (R3)
|
||||
34 => ColorData::new(data, version, 0x69, Some(0x16b), Some(0x280), Some(0x281)),
|
||||
// 48 (R7/R10)
|
||||
48 => ColorData::new(data, version, 0x69, Some(0x16b), Some(0x281), Some(0x282)),
|
||||
// 64 (R5MK2), 66 (R6MK3)
|
||||
64 | 66 => ColorData::new(data, version, 0x69, Some(0x17f), Some(0x294), Some(0x295)),
|
||||
|
||||
_ => return Err(format!("Unknown COLORDATA version: {}", data[0]).into()),
|
||||
})
|
||||
}
|
||||
|
||||
_ => Err(RawlerError::DecoderFailed(format!(
|
||||
"Invalid COLORDATA tag: type {}",
|
||||
colordata.value_type_name()
|
||||
))),
|
||||
}
|
||||
}
|
||||
+796
@@ -0,0 +1,796 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use image::DynamicImage;
|
||||
use log::{debug, warn};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::bits::Endian;
|
||||
use crate::decoders::*;
|
||||
use crate::decompressors::crx::decompress_crx_image;
|
||||
use crate::envparams::{rawler_crx_raw_trak, rawler_ignore_previews};
|
||||
use crate::exif::ExifGPS;
|
||||
use crate::formats::bmff::FileBox;
|
||||
use crate::formats::bmff::ext_cr3::cmp1::Cmp1Box;
|
||||
use crate::formats::bmff::ext_cr3::cr3desc::Cr3DescBox;
|
||||
use crate::formats::bmff::ext_cr3::iad1::{Iad1Box, Iad1Type};
|
||||
use crate::formats::bmff::trak::TrakBox;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::formats::tiff::{Entry, GenericTiffReader, Rational, Value};
|
||||
use crate::imgop::{Point, Rect};
|
||||
use crate::lens::{LensDescription, LensId, LensResolver};
|
||||
use crate::{RawImage, pumps::ByteStream};
|
||||
|
||||
const CANON_CN_MOUNT: &str = "cn-mount";
|
||||
const CANON_EF_MOUNT: &str = "ef-mount";
|
||||
const CANON_RF_MOUNT: &str = "rf-mount";
|
||||
|
||||
/// Decoder for CR3 and CRM files
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cr3Decoder<'a> {
|
||||
camera: Camera,
|
||||
rawloader: &'a RawLoader,
|
||||
bmff: Bmff,
|
||||
// Basic EXIF information
|
||||
cmt1: GenericTiffReader,
|
||||
// EXIF
|
||||
cmt2: GenericTiffReader,
|
||||
// Makernotes
|
||||
cmt3: GenericTiffReader,
|
||||
// GPS
|
||||
cmt4: GenericTiffReader,
|
||||
// Metadata cache
|
||||
md_cache: DecoderCache<Cr3Metadata>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Cr3Metadata {
|
||||
ctmd_exposure: Option<CtmdExposureInfo>,
|
||||
ctmd_focallen: Option<Rational>,
|
||||
ctmd_rec7_exif: Option<GenericTiffReader>,
|
||||
ctmd_rec7_makernotes: Option<GenericTiffReader>,
|
||||
// CTMD Makernotes: COLORDATA
|
||||
ctmd_rec8: Option<GenericTiffReader>,
|
||||
ctmd_rec9: Option<GenericTiffReader>,
|
||||
xpacket: Option<Vec<u8>>,
|
||||
image_unique_id: Option<[u8; 16]>,
|
||||
lens_description: Option<&'static LensDescription>,
|
||||
exif: Option<GenericTiffReader>,
|
||||
makernotes: Option<GenericTiffReader>,
|
||||
wb: Option<[f32; 4]>,
|
||||
blacklevels: Option<[u16; 4]>,
|
||||
whitelevel: Option<u16>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const CR3_CTMD_BLOCK_EXIFIFD: u16 = 0x8769;
|
||||
const CR3_CTMD_BLOCK_MAKERNOTES: u16 = 0x927c;
|
||||
|
||||
/// Type values fro CCTP records
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum Cr3ImageType {
|
||||
CrxBix = 0,
|
||||
CrxSmall = 1,
|
||||
PreviewBig = 2,
|
||||
Ctmd = 3,
|
||||
CrxDual = 4,
|
||||
}
|
||||
|
||||
impl<'a> Cr3Decoder<'a> {
|
||||
/// Construct new CR3 or CRM deocder
|
||||
pub fn new(_rawfile: &RawSource, bmff: Bmff, rawloader: &'a RawLoader) -> Result<Cr3Decoder<'a>> {
|
||||
if let Some(Cr3DescBox { cmt1, cmt2, cmt3, cmt4, .. }) = bmff.filebox.moov.cr3desc.as_ref() {
|
||||
let mode = Self::get_mode(cmt3.tiff.root_ifd())?;
|
||||
let camera = rawloader.check_supported_with_mode(cmt1.tiff.root_ifd(), mode)?;
|
||||
let decoder = Cr3Decoder {
|
||||
camera,
|
||||
rawloader,
|
||||
cmt1: cmt1.tiff.clone(),
|
||||
cmt2: cmt2.tiff.clone(),
|
||||
cmt3: cmt3.tiff.clone(),
|
||||
cmt4: cmt4.tiff.clone(),
|
||||
bmff,
|
||||
md_cache: DecoderCache::new(),
|
||||
};
|
||||
Ok(decoder)
|
||||
} else {
|
||||
Err("It's not a CR3 file: CMT boxes not found.".into())
|
||||
}
|
||||
}
|
||||
|
||||
// Search for quality tag inside makernotes and derive
|
||||
// our mode string for configuration.
|
||||
fn get_mode(makernotes: &IFD) -> Result<&str> {
|
||||
Ok(if let Some(entry) = makernotes.get_entry(0x0001) {
|
||||
match entry.force_u16(3) {
|
||||
4 => "raw",
|
||||
7 => "craw",
|
||||
130 => "crm",
|
||||
131 => "crm",
|
||||
_ => "undefined",
|
||||
}
|
||||
} else {
|
||||
"undefined"
|
||||
})
|
||||
}
|
||||
|
||||
/// Get trak from moov box
|
||||
fn moov_trak(&self, trak_id: usize) -> Option<&TrakBox> {
|
||||
self.bmff.filebox.moov.traks.get(trak_id)
|
||||
}
|
||||
|
||||
/// Get IAD1 box for specific trak
|
||||
fn iad1_box(&self, trak_idx: usize) -> Option<&Iad1Box> {
|
||||
let trak = &self.bmff.filebox.moov.traks[trak_idx];
|
||||
let craw = trak.mdia.minf.stbl.stsd.craw.as_ref();
|
||||
craw.and_then(|craw| craw.cdi1.as_ref()).map(|cdi1| &cdi1.iad1)
|
||||
}
|
||||
|
||||
/// Get CMP1 box for specific trak
|
||||
fn cmp1_box(&self, trak_idx: usize) -> Option<&Cmp1Box> {
|
||||
let trak = &self.bmff.filebox.moov.traks[trak_idx];
|
||||
let craw = trak.mdia.minf.stbl.stsd.craw.as_ref();
|
||||
craw.and_then(|craw| craw.cmp1.as_ref())
|
||||
}
|
||||
|
||||
/// Read CTMD records for given sample
|
||||
/// Each sample (for movie files) have their own CTMD records
|
||||
fn read_ctmd(&self, rawfile: &RawSource, sample_idx: u32) -> Result<Option<Ctmd>> {
|
||||
// Search for a trak which has a CTMD box (there should be only one)
|
||||
if let Some(ctmd_trak_index) = self
|
||||
.bmff
|
||||
.filebox
|
||||
.moov
|
||||
.traks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, trak)| trak.mdia.minf.stbl.stsd.ctmd.is_some())
|
||||
.map(|(id, _)| id)
|
||||
{
|
||||
log::debug!("CTMD trak_index: {}", ctmd_trak_index);
|
||||
let ctmd_trak = &self.bmff.filebox.moov.traks[ctmd_trak_index];
|
||||
let (offset, size) = ctmd_trak
|
||||
.mdia
|
||||
.minf
|
||||
.stbl
|
||||
.get_sample_offset(sample_idx as u32 + 1)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("CTMD sample index {} out of bound", sample_idx)))?;
|
||||
debug!("CR3 CTMD mdat offset for sample_idx {}: {}, len: {}", sample_idx, offset, size);
|
||||
let buf = rawfile
|
||||
.subview(offset as u64, size as u64)
|
||||
.map_err(|e| RawlerError::with_io_error("CR3: failed to read CTMD", rawfile.path(), e))?;
|
||||
|
||||
//dump_buf("/tmp/ctmd.buf", &buf);
|
||||
let mut substream = ByteStream::new(buf, Endian::Little);
|
||||
let ctmd = Ctmd::new(&mut substream);
|
||||
Ok(Some(ctmd))
|
||||
} else {
|
||||
log::warn!("No CTMD trak found");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Cr3Format {
|
||||
pub filebox: FileBox,
|
||||
}
|
||||
|
||||
impl<'a> Decoder for Cr3Decoder<'a> {
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
FormatDump::Cr3(Cr3Format {
|
||||
filebox: self.bmff.filebox.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, file: &RawSource, params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let cr3md = self.read_cr3_metadata(file, params)?;
|
||||
|
||||
let mut exif = Exif::default();
|
||||
exif.extend_from_ifd(self.cmt1.root_ifd())?;
|
||||
exif.extend_from_ifd(self.cmt2.root_ifd())?;
|
||||
exif.extend_from_gps_ifd(self.cmt4.root_ifd())?;
|
||||
|
||||
if exif.gps.is_none() {
|
||||
exif.gps = Some(ExifGPS::default());
|
||||
}
|
||||
|
||||
if let Some(gps) = &mut exif.gps {
|
||||
for (tag, entry) in self.cmt4.root_ifd().entries() {
|
||||
match tag {
|
||||
// Special handling for Exif.GPSInfo.GPSLatitude and Exif.GPSInfo.GPSLongitude.
|
||||
// Exif.GPSInfo.GPSTimeStamp is wrong, too and can be fixed with the same logic.
|
||||
// Canon CR3 contains only two rationals, but these tags are specified as a vector
|
||||
// of three reationals (degrees, minutes, seconds).
|
||||
// We fix this by extending with 0/1 as seconds value.
|
||||
0x0002 | 0x0004 | 0x0007 => match &entry.value {
|
||||
Value::Rational(v) => {
|
||||
let fixed_value = if v.len() == 2 { vec![v[0], v[1], Rational::new(0, 1)] } else { v.clone() };
|
||||
match tag {
|
||||
0x0002 => gps.gps_latitude = fixed_value.try_into().ok(),
|
||||
0x0004 => gps.gps_longitude = fixed_value.try_into().ok(),
|
||||
0x0007 => gps.gps_timestamp = fixed_value.try_into().ok(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("CR3: Exif.GPSInfo.GPSLatitude and Exif.GPSInfo.GPSLongitude expected to be of type RATIONAL, GPS data is ignored");
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut mdata = RawMetadata::new_with_lens(&self.camera, exif, cr3md.lens_description.cloned());
|
||||
|
||||
if let Some(unique_id) = &cr3md.image_unique_id {
|
||||
// For CR3, we use the already included Makernote tag with unique image ID
|
||||
mdata.unique_image_id = Some(u128::from_le_bytes(*unique_id));
|
||||
}
|
||||
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn xpacket(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<Vec<u8>>> {
|
||||
let cr3md = self.read_cr3_metadata(file, params)?;
|
||||
Ok(cr3md.xpacket)
|
||||
}
|
||||
|
||||
/// CR3 can store multiple samples in trak
|
||||
fn raw_image_count(&self) -> Result<usize> {
|
||||
let raw_trak_id = rawler_crx_raw_trak()
|
||||
.or_else(|| self.get_trak_index(Cr3ImageType::CrxBix))
|
||||
.ok_or("Unable to find trak index")?;
|
||||
let moov_trak = self.moov_trak(raw_trak_id).ok_or(format!("Unable to get MOOV trak {}", raw_trak_id))?;
|
||||
Ok(moov_trak.mdia.minf.stbl.stsz.sample_count as usize)
|
||||
}
|
||||
|
||||
/// Decode raw image
|
||||
fn raw_image(&self, file: &RawSource, params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let sample_idx = params.image_index;
|
||||
if sample_idx >= self.raw_image_count()? {
|
||||
return Err(RawlerError::DecoderFailed(format!(
|
||||
"Raw image index {} out of range ({})",
|
||||
sample_idx,
|
||||
self.raw_image_count()?
|
||||
)));
|
||||
}
|
||||
|
||||
let cr3md = self.read_cr3_metadata(file, params)?;
|
||||
|
||||
if let Some(cr3desc) = &self.bmff.filebox.moov.cr3desc {
|
||||
for item in cr3desc.cctp.ccdts.iter() {
|
||||
log::debug!("CCDT: trak {} type {} dual {}", item.trak_index, item.image_type, item.dual_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
let raw_trak_id = rawler_crx_raw_trak()
|
||||
.or_else(|| self.get_trak_index(Cr3ImageType::CrxBix))
|
||||
.ok_or("Unable to find trak index")?;
|
||||
|
||||
// Load trak with raw MDAT section
|
||||
let moov_trak = self.moov_trak(raw_trak_id).ok_or(format!("Unable to get MOOV trak {}", raw_trak_id))?;
|
||||
let (offset, size) = moov_trak
|
||||
.mdia
|
||||
.minf
|
||||
.stbl
|
||||
.get_sample_offset(sample_idx as u32 + 1)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("stbl sample not found")))?;
|
||||
debug!("RAW mdat offset: {}, len: {}", offset, size);
|
||||
// Raw data buffer
|
||||
let buf = file
|
||||
.subview(offset as u64, size as u64)
|
||||
.map_err(|e| RawlerError::with_io_error("CR3: failed to read raw data", file.path(), e))?;
|
||||
|
||||
let cmp1 = self.cmp1_box(raw_trak_id).ok_or(format!("CMP1 box not found for trak {}", raw_trak_id))?;
|
||||
debug!("cmp1 mdat hdr size: {}", cmp1.mdat_hdr_size);
|
||||
|
||||
let mut wb = cr3md.wb.unwrap_or_else(|| {
|
||||
// This is known for R5 C CRM Standard-Raw files
|
||||
log::warn!("No WB info in CR3 metadata found, fallback to 1.0 coefficients");
|
||||
[1.0, 1.0, 1.0, f32::NAN]
|
||||
});
|
||||
let whitelevel = cr3md.whitelevel.unwrap_or(((1_u32 << self.camera.bps.unwrap_or(16)) - 1) as u16);
|
||||
|
||||
// Special handling for CRM movie files
|
||||
if let Some(entry) = self.cmt3.get_entry(0x0001) {
|
||||
if 130 == entry.force_u16(3) {
|
||||
// Light Raw
|
||||
// WB is already applied, use 1.0
|
||||
wb = [1.0, 1.0, 1.0, f32::NAN];
|
||||
}
|
||||
if 131 == entry.force_u16(3) { // Standard Raw
|
||||
// Nothing special for Standard raw
|
||||
}
|
||||
}
|
||||
|
||||
let image = if !dummy {
|
||||
PixU16::new_with(
|
||||
decompress_crx_image(buf, cmp1).map_err(|e| format!("Failed to decode raw: {}", e))?,
|
||||
cmp1.f_width as usize,
|
||||
cmp1.f_height as usize,
|
||||
)
|
||||
} else {
|
||||
PixU16::new_uninit(cmp1.f_width as usize, cmp1.f_height as usize)
|
||||
};
|
||||
|
||||
let cpp = 1;
|
||||
let blacklevel = cr3md
|
||||
.blacklevels
|
||||
.map(|x| BlackLevel::new(&x, self.camera.cfa.width, self.camera.cfa.height, cpp));
|
||||
let whitelevel = WhiteLevel(vec![whitelevel as u32]);
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera));
|
||||
let mut img = RawImage::new(self.camera.clone(), image, cpp, wb, photometric, blacklevel, Some(whitelevel), dummy);
|
||||
|
||||
// IAD1 box contains sensor information
|
||||
// We use the sensor crop from IAD1 as recommended image crop.
|
||||
// The same crop is used as ActiveArea, because black areas in IAD1 are not
|
||||
// correct (they differs like 4-6 pixels from real values).
|
||||
match self.iad1_box(raw_trak_id) {
|
||||
Some(iad1) => {
|
||||
match &iad1.iad1_type {
|
||||
// IAD1 (small, used for CRM movie files)
|
||||
Iad1Type::Small(small) => {
|
||||
img.crop_area = Some(Rect::new_with_points(
|
||||
Point::new(small.crop_left_offset as usize, small.crop_top_offset as usize),
|
||||
Point::new((small.crop_right_offset + 1) as usize, (small.crop_bottom_offset + 1) as usize),
|
||||
));
|
||||
img.active_area = img.crop_area;
|
||||
}
|
||||
// IAD1 (big, used for full size raws)
|
||||
Iad1Type::Big(big) => {
|
||||
img.crop_area = Some(Rect::new_with_points(
|
||||
Point::new(big.crop_left_offset as usize, big.crop_top_offset as usize),
|
||||
Point::new((big.crop_right_offset + 1) as usize, (big.crop_bottom_offset + 1) as usize),
|
||||
));
|
||||
|
||||
// For uncropped files this is fine, but for 1.6 crop files, the dimension is wrong.
|
||||
// For example, R5 crop is total height of 3510, but active_area_bottom_offset is 3512.
|
||||
let rect = {
|
||||
// Limit the offsets to image bounds.
|
||||
// Probably broken firmware, glitches in sensor size calculation or I'm just making
|
||||
// wrong asumptions...
|
||||
let right = usize::min(cmp1.f_width as usize, (big.active_area_right_offset - 1) as usize);
|
||||
let bottom = usize::min(cmp1.f_height as usize, (big.active_area_bottom_offset - 1) as usize);
|
||||
Rect::new_with_points(
|
||||
Point::new(big.active_area_left_offset as usize, big.active_area_top_offset as usize),
|
||||
Point::new(right, bottom),
|
||||
)
|
||||
};
|
||||
log::debug!("IAD1 active area: {:?}", rect);
|
||||
img.active_area = Some(rect);
|
||||
//img.active_area = img.crop_area;
|
||||
|
||||
let blackarea_h = Rect::new_with_points(
|
||||
Point::new(big.lob_left_offset as usize, big.lob_top_offset as usize),
|
||||
Point::new((big.lob_right_offset - 1) as usize, (big.lob_bottom_offset - 1) as usize),
|
||||
);
|
||||
if !blackarea_h.is_empty() {
|
||||
//img.blackareas.push(blackarea_h);
|
||||
}
|
||||
let blackarea_v = Rect::new_with_points(
|
||||
Point::new(big.tob_left_offset as usize, big.tob_top_offset as usize),
|
||||
Point::new((big.tob_right_offset - 1) as usize, (big.tob_bottom_offset - 1) as usize),
|
||||
);
|
||||
if !blackarea_v.is_empty() {
|
||||
//img.blackareas.push(blackarea_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
warn!("No IAD1 box found for sensor data");
|
||||
}
|
||||
}
|
||||
debug!("Canon active area: {:?}", img.active_area);
|
||||
debug!("Canon crop area: {:?}", img.crop_area);
|
||||
debug!("Black areas: {:?}", img.blackareas);
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
/// Extract preview image embedded in CR3
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if rawler_ignore_previews() {
|
||||
return Err(RawlerError::DecoderFailed("Unable to extract preview image".into()));
|
||||
}
|
||||
let offset = self.bmff.filebox.moov.traks[0].mdia.minf.stbl.co64.as_ref().expect("co64 box").entries[0] as usize;
|
||||
let size = self.bmff.filebox.moov.traks[0].mdia.minf.stbl.stsz.sample_sizes[0] as usize;
|
||||
debug!("JPEG preview mdat offset: {}, len: {}", offset, size);
|
||||
let buf = file
|
||||
.subview(offset as u64, size as u64)
|
||||
.map_err(|e| RawlerError::with_io_error("CR3: failed to read full image data", file.path(), e))?;
|
||||
match image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg) {
|
||||
Ok(img) => Ok(Some(img)),
|
||||
Err(e) => {
|
||||
debug!("TRAK 0 contains no JPEG preview, is it PQ/HEIF? Error: {}", e);
|
||||
Err(RawlerError::DecoderFailed(
|
||||
"Unable to extract preview image from CR3 HDR-PQ file. Please see 'https://github.com/dnglab/dnglab/issues/7'".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::CR3
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Cr3Decoder<'a> {
|
||||
fn read_lens_id(&self) -> Result<LensId> {
|
||||
let mut id = (0, 0);
|
||||
if let Some(Entry { value: Value::Short(v), .. }) = self.cmt3.get_entry(Cr3MakernoteTag::CameraSettings) {
|
||||
id.0 = v[22] as u32;
|
||||
}
|
||||
|
||||
if let Some(Entry { value: Value::Short(v), .. }) = self.cmt3.get_entry(Cr3MakernoteTag::FileInfo) {
|
||||
id.1 = v[61] as u32;
|
||||
}
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_lens_name(&self) -> Result<Option<&String>> {
|
||||
if let Some(Entry {
|
||||
value: crate::formats::tiff::Value::Ascii(lens_id),
|
||||
..
|
||||
}) = self.cmt2.get_entry(ExifTag::LensModel)
|
||||
{
|
||||
return Ok(lens_id.strings().get(0));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn read_cr3_metadata(&self, rawfile: &RawSource, params: &RawDecodeParams) -> Result<Cr3Metadata> {
|
||||
if let Some(md) = self.md_cache.get(params) {
|
||||
return Ok(md);
|
||||
}
|
||||
let mut md = Cr3Metadata::default();
|
||||
|
||||
let resolver = LensResolver::new()
|
||||
.with_lens_keyname(self.read_lens_name()?)
|
||||
.with_camera(&self.camera) // must follow with_lens_keyname() as it my override key
|
||||
.with_lens_id(self.read_lens_id()?)
|
||||
.with_mounts(&[CANON_CN_MOUNT.into(), CANON_EF_MOUNT.into(), CANON_RF_MOUNT.into()]);
|
||||
md.lens_description = resolver.resolve();
|
||||
|
||||
if let Some(Entry {
|
||||
value: crate::formats::tiff::Value::Byte(v),
|
||||
..
|
||||
}) = self.cmt3.get_entry(Cr3MakernoteTag::ImgUniqueID)
|
||||
{
|
||||
if v.len() == 16 {
|
||||
debug!("CR3 makernote ImgUniqueID: {:x?}", v);
|
||||
md.image_unique_id = Some(v.as_slice().try_into().expect("Invalid slice size"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(entry) = self.cmt3.get_entry(0x0001) {
|
||||
let quality = match entry.force_u16(3) {
|
||||
4 => "RAW",
|
||||
5 => "Superfine",
|
||||
7 => "CRAW",
|
||||
130 => "LightRaw",
|
||||
131 => "StandardRaw",
|
||||
_ => "unknown",
|
||||
};
|
||||
debug!("Canon quality mode: {}", quality);
|
||||
}
|
||||
|
||||
if let Some(entry) = self.cmt3.get_entry(0x4026) {
|
||||
let clog = match entry.force_u32(11) {
|
||||
0 => "OFF",
|
||||
1 => "CLog1",
|
||||
2 => "CLog2",
|
||||
3 => "CLog3",
|
||||
_ => "unknown",
|
||||
};
|
||||
debug!("Canon CLog mode: {}", clog);
|
||||
}
|
||||
|
||||
if let Some(xpacket_box) = self.bmff.filebox.cr3xpacket.as_ref() {
|
||||
let offset = xpacket_box.header.offset + xpacket_box.header.header_len;
|
||||
let size = xpacket_box.header.size - xpacket_box.header.header_len;
|
||||
let buf = rawfile
|
||||
.subview(offset, size)
|
||||
.map_err(|e| RawlerError::with_io_error("CR3: failed to read XPACKET", rawfile.path(), e))?;
|
||||
md.xpacket = Some(buf.to_vec());
|
||||
}
|
||||
|
||||
if let Some(ctmd) = self.read_ctmd(rawfile, params.image_index as u32)? {
|
||||
if let Some(rec5) = ctmd.exposure_info()? {
|
||||
debug!("CTMD Rec(5): {:?}", rec5);
|
||||
md.ctmd_exposure = Some(rec5);
|
||||
}
|
||||
md.ctmd_focallen = ctmd.focal_len()?;
|
||||
|
||||
if let Some(rec7) = ctmd.get_as_tiff(7, CR3_CTMD_BLOCK_EXIFIFD)? {
|
||||
md.ctmd_rec7_exif = Some(rec7);
|
||||
}
|
||||
if let Some(rec7) = ctmd.get_as_tiff(7, CR3_CTMD_BLOCK_MAKERNOTES)? {
|
||||
md.ctmd_rec7_makernotes = Some(rec7);
|
||||
}
|
||||
if let Some(rec8) = ctmd.get_as_tiff(8, CR3_CTMD_BLOCK_MAKERNOTES)? {
|
||||
if let Some(colordata) = rec8.get_entry(Cr3MakernoteTag::ColorData) {
|
||||
let colordata = cr2::parse_colordata(colordata)?;
|
||||
//rec8.root_ifd().dump::<TiffCommonTag>(10).iter().for_each(|line| eprintln!("MKD: {}", line));
|
||||
md.wb = Some(normalize_wb(colordata.wb));
|
||||
md.blacklevels = colordata.blacklevel;
|
||||
md.whitelevel = colordata.specular_whitelevel;
|
||||
/*
|
||||
if let crate::formats::tiff::Value::Short(v) = &levels.value {
|
||||
if let Some(offset) = self.camera.param_usize("colordata_wbcoeffs") {
|
||||
let raw_wb = [v[offset] as f32, v[offset + 1] as f32, v[offset + 2] as f32, v[offset + 3] as f32];
|
||||
md.wb = Some(normalize_wb(raw_wb));
|
||||
}
|
||||
if let Some(offset) = self.camera.param_usize("colordata_blacklevel") {
|
||||
debug!("Blacklevel offset: {:x}", offset);
|
||||
md.blacklevels = Some([v[offset], v[offset + 1], v[offset + 2], v[offset + 3]]);
|
||||
}
|
||||
if let Some(offset) = self.camera.param_usize("colordata_whitelevel") {
|
||||
md.whitelevel = Some(v[offset]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
md.ctmd_rec8 = Some(rec8);
|
||||
}
|
||||
if let Some(rec9) = ctmd.get_as_tiff(9, CR3_CTMD_BLOCK_MAKERNOTES)? {
|
||||
md.ctmd_rec9 = Some(rec9);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("CR3 blacklevels: {:?}", md.blacklevels);
|
||||
debug!("CR3 whitelevel: {:?}", md.whitelevel);
|
||||
|
||||
self.md_cache.set(params, md.clone());
|
||||
Ok(md)
|
||||
}
|
||||
|
||||
fn get_trak_index(&self, image_type: Cr3ImageType) -> Option<usize> {
|
||||
if let Some(cr3desc) = &self.bmff.filebox.moov.cr3desc {
|
||||
cr3desc.cctp.ccdts.iter().find(|ccdt| ccdt.image_type == image_type as u64).map(|rec| {
|
||||
debug_assert!(rec.trak_index > 0);
|
||||
(rec.trak_index - 1) as usize
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CTMD section with multiple records
|
||||
#[derive(Clone, Debug)]
|
||||
struct Ctmd {
|
||||
pub records: HashMap<u16, CtmdRecord>,
|
||||
}
|
||||
|
||||
/// Record inside CTMD section
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct CtmdRecord {
|
||||
pub rec_size: u32,
|
||||
pub rec_type: u16,
|
||||
pub reserved1: u8,
|
||||
pub reserved2: u8,
|
||||
pub reserved3: u8,
|
||||
pub reserved4: u8,
|
||||
pub reserved5: i8,
|
||||
pub reserved6: i8,
|
||||
pub payload: Vec<u8>,
|
||||
pub blocks: HashMap<u16, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Ctmd {
|
||||
pub fn new(data: &mut ByteStream) -> Self {
|
||||
let mut records = HashMap::new();
|
||||
|
||||
while data.remaining_bytes() >= 12 {
|
||||
let size = data.get_u32();
|
||||
let mut rec = CtmdRecord {
|
||||
rec_size: size,
|
||||
rec_type: data.get_u16(),
|
||||
reserved1: data.get_u8(),
|
||||
reserved2: data.get_u8(),
|
||||
reserved3: data.get_u8(),
|
||||
reserved4: data.get_u8(),
|
||||
reserved5: data.get_i8(),
|
||||
reserved6: data.get_i8(),
|
||||
payload: data.get_bytes(size as usize - 12),
|
||||
blocks: HashMap::new(),
|
||||
};
|
||||
debug!(
|
||||
"CTMD Rec {:02}: {}, {}, {}, {}, {}, {}",
|
||||
rec.rec_type, rec.reserved1, rec.reserved2, rec.reserved3, rec.reserved4, rec.reserved5, rec.reserved6
|
||||
);
|
||||
//dump_buf(&format!("/tmp/ctmd_rec{}.bin", rec.rec_type), rec.payload.as_slice());
|
||||
if [7, 8, 9, 10, 11, 12].contains(&rec.rec_type) {
|
||||
let mut bs = ByteStream::new(rec.payload.as_slice(), Endian::Little);
|
||||
let mut _block_id = 0;
|
||||
while bs.remaining_bytes() >= (4 + 2 + 2) {
|
||||
let sz = bs.get_u32() as usize;
|
||||
let tag = bs.get_u16();
|
||||
let _uk = bs.get_u16();
|
||||
if sz >= 8 && bs.remaining_bytes() >= (sz - 8) {
|
||||
log::debug!("CTMD BLOCK: size {}, tag {}, remaining: {}", sz, tag, bs.remaining_bytes());
|
||||
let data = bs.get_bytes(sz as usize - 8);
|
||||
//dump_buf(&format!("/tmp/ctmd_rec{}_block{}_tag0x{:X}_uk{:X}.bin", rec.rec_type, block_id, tag, uk), data.as_slice());
|
||||
if [CR3_CTMD_BLOCK_EXIFIFD, CR3_CTMD_BLOCK_MAKERNOTES].contains(&tag) {
|
||||
assert_eq!(rec.blocks.contains_key(&tag), false, "Double tag found?!");
|
||||
rec.blocks.insert(tag, data);
|
||||
}
|
||||
_block_id += 1;
|
||||
} else {
|
||||
log::debug!(
|
||||
"CTMD BLOCK: size {}, tag {}, remaining: {} - is invalid, maybe not a block",
|
||||
sz,
|
||||
tag,
|
||||
bs.remaining_bytes()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("CTMD record type {} unknown, ignoring.", rec.rec_type);
|
||||
//dump_buf(&format!("/tmp/ctmd_rec{}.bin", rec.rec_type), rec.payload.as_slice());
|
||||
}
|
||||
records.insert(rec.rec_type, rec);
|
||||
}
|
||||
Self { records }
|
||||
}
|
||||
|
||||
pub fn get_as_tiff(&self, record: u16, tag: u16) -> Result<Option<GenericTiffReader>> {
|
||||
if let Some(block) = self.records.get(&record).and_then(|rec| rec.blocks.get(&tag)) {
|
||||
Ok(Some(GenericTiffReader::new_with_buffer(block, 0, 0, Some(0))?))
|
||||
} else {
|
||||
warn!("Unable to find CTMD record {}, tag 0x{:X}", record, tag);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exposure_info(&self) -> Result<Option<CtmdExposureInfo>> {
|
||||
if let Some(rec) = self.records.get(&5) {
|
||||
let mut buf = ByteStream::new(rec.payload.as_slice(), Endian::Little);
|
||||
let fnumber = Rational::new(buf.get_u16().into(), buf.get_u16().into());
|
||||
let exposure = Rational::new(buf.get_u16().into(), buf.get_u16().into());
|
||||
let iso_speed = buf.get_u32();
|
||||
let unknown = buf.get_bytes(buf.remaining_bytes());
|
||||
Ok(Some(CtmdExposureInfo {
|
||||
fnumber,
|
||||
exposure,
|
||||
iso_speed,
|
||||
unknown,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focal_len(&self) -> Result<Option<Rational>> {
|
||||
if let Some(rec) = self.records.get(&4) {
|
||||
let mut buf = ByteStream::new(rec.payload.as_slice(), Endian::Little);
|
||||
let focal_len = Rational::new(buf.get_u16().into(), buf.get_u16().into());
|
||||
Ok(Some(focal_len))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
debug!("CR3 raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1]; // G1 should be 1024 and we use this as divisor
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CtmdExposureInfo {
|
||||
pub fnumber: Rational,
|
||||
pub exposure: Rational,
|
||||
pub iso_speed: u32,
|
||||
pub unknown: Vec<u8>,
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(Cr3MakernoteTag);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum Cr3MakernoteTag {
|
||||
CameraSettings = 0x0001,
|
||||
FocusInfo = 0x0002,
|
||||
FlashInfo = 0x0003,
|
||||
ShotInfo = 0x0004,
|
||||
Panorama = 0x0005,
|
||||
ImageType = 0x0006,
|
||||
FirmareVer = 0x0007,
|
||||
FileNumber = 0x0008,
|
||||
OwnerName = 0x0009,
|
||||
UnknownD30 = 0x000a,
|
||||
SerialNum = 0x000c,
|
||||
CameraInfo = 0x000d,
|
||||
FileLen = 0x000e,
|
||||
CustomFunc = 0x000f,
|
||||
ModelId = 0x0010,
|
||||
MovieInfo = 0x0011,
|
||||
AFInfo = 0x0012,
|
||||
ThumbArea = 0x0013,
|
||||
SerialFormat = 0x0014,
|
||||
SuperMacro = 0x001a,
|
||||
DateStampMode = 0x001c,
|
||||
MyColors = 0x001d,
|
||||
FirmwareRev = 0x001e,
|
||||
Categories = 0x0023,
|
||||
FaceDetect1 = 0x0024,
|
||||
FaceDetect2 = 0x0025,
|
||||
AFInfo2 = 0x0026,
|
||||
ContrastInfo = 0x0027,
|
||||
ImgUniqueID = 0x0028,
|
||||
WBInfo = 0x0029,
|
||||
FaceDetect3 = 0x002f,
|
||||
TimeInfo = 0x0035,
|
||||
BatteryType = 0x0038,
|
||||
AFInfo3 = 0x003c,
|
||||
RawDataOffset = 0x0081,
|
||||
OrigDecisionDataOffset = 0x0083,
|
||||
CustomFunc1D = 0x0090,
|
||||
PersFunc = 0x0091,
|
||||
PersFuncValues = 0x0092,
|
||||
FileInfo = 0x0093,
|
||||
AFPointsInFocus1D = 0x0094,
|
||||
LensModel = 0x0095,
|
||||
InternalSerial = 0x0096,
|
||||
DustRemovalData = 0x0097,
|
||||
CropInfo = 0x0098,
|
||||
CustomFunc2 = 0x0099,
|
||||
AspectInfo = 0x009a,
|
||||
ProcessingInfo = 0x00a0,
|
||||
ToneCurveTable = 0x00a1,
|
||||
SharpnessTable = 0x00a2,
|
||||
SharpnessFreqTable = 0x00a3,
|
||||
WhiteBalanceTable = 0x00a4,
|
||||
ColorBalance = 0x00a9,
|
||||
MeasuredColor = 0x00aa,
|
||||
ColorTemp = 0x00ae,
|
||||
CanonFlags = 0x00b0,
|
||||
ModifiedInfo = 0x00b1,
|
||||
TnoeCurveMatching = 0x00b2,
|
||||
WhiteBalanceMatching = 0x00b3,
|
||||
ColorSpace = 0x00b4,
|
||||
PreviewImageInfo = 0x00b6,
|
||||
VRDOffset = 0x00d0,
|
||||
SensorInfo = 0x00e0,
|
||||
ColorData = 0x4001,
|
||||
CRWParam = 0x4002,
|
||||
ColorInfo = 0x4003,
|
||||
Flavor = 0x4005,
|
||||
PictureStyleUserDef = 0x4008,
|
||||
PictureStylePC = 0x4009,
|
||||
CustomPictureStyleFileName = 0x4010,
|
||||
AFMicroAdj = 0x4013,
|
||||
VignettingCorr = 0x4015,
|
||||
VignettingCorr2 = 0x4016,
|
||||
LightningOpt = 0x4018,
|
||||
LensInfo = 0x4019,
|
||||
AmbienceInfo = 0x4020,
|
||||
MultiExp = 0x4021,
|
||||
FilterInfo = 0x4024,
|
||||
HDRInfo = 0x4025,
|
||||
AFConfig = 0x4028,
|
||||
RawBurstModeRoll = 0x403f,
|
||||
}
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
use crate::RawImage;
|
||||
use crate::decoders::*;
|
||||
use crate::decompressors::ljpeg::huffman::*;
|
||||
use crate::formats::ciff::*;
|
||||
use crate::packed::*;
|
||||
use crate::pumps::BitPump;
|
||||
use crate::pumps::BitPumpJPEG;
|
||||
|
||||
const CRW_FIRST_TREE: [[u8; 29]; 3] = [
|
||||
[
|
||||
0, 1, 4, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x03, 0x05, 0x06, 0x02, 0x07, 0x01, 0x08, 0x09, 0x00, 0x0a, 0x0b, 0xff,
|
||||
],
|
||||
[
|
||||
0, 2, 2, 3, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x03, 0x02, 0x04, 0x01, 0x05, 0x00, 0x06, 0x07, 0x09, 0x08, 0x0a, 0x0b, 0xff,
|
||||
],
|
||||
[
|
||||
0, 0, 6, 3, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x06, 0x05, 0x07, 0x04, 0x08, 0x03, 0x09, 0x02, 0x00, 0x0a, 0x01, 0x0b, 0xff,
|
||||
],
|
||||
];
|
||||
|
||||
const CRW_SECOND_TREE: [[u8; 180]; 3] = [
|
||||
[
|
||||
0, 2, 2, 2, 1, 4, 2, 1, 2, 5, 1, 1, 0, 0, 0, 139, 0x03, 0x04, 0x02, 0x05, 0x01, 0x06, 0x07, 0x08, 0x12, 0x13, 0x11, 0x14, 0x09, 0x15, 0x22, 0x00, 0x21,
|
||||
0x16, 0x0a, 0xf0, 0x23, 0x17, 0x24, 0x31, 0x32, 0x18, 0x19, 0x33, 0x25, 0x41, 0x34, 0x42, 0x35, 0x51, 0x36, 0x37, 0x38, 0x29, 0x79, 0x26, 0x1a, 0x39, 0x56,
|
||||
0x57, 0x28, 0x27, 0x52, 0x55, 0x58, 0x43, 0x76, 0x59, 0x77, 0x54, 0x61, 0xf9, 0x71, 0x78, 0x75, 0x96, 0x97, 0x49, 0xb7, 0x53, 0xd7, 0x74, 0xb6, 0x98, 0x47,
|
||||
0x48, 0x95, 0x69, 0x99, 0x91, 0xfa, 0xb8, 0x68, 0xb5, 0xb9, 0xd6, 0xf7, 0xd8, 0x67, 0x46, 0x45, 0x94, 0x89, 0xf8, 0x81, 0xd5, 0xf6, 0xb4, 0x88, 0xb1, 0x2a,
|
||||
0x44, 0x72, 0xd9, 0x87, 0x66, 0xd4, 0xf5, 0x3a, 0xa7, 0x73, 0xa9, 0xa8, 0x86, 0x62, 0xc7, 0x65, 0xc8, 0xc9, 0xa1, 0xf4, 0xd1, 0xe9, 0x5a, 0x92, 0x85, 0xa6,
|
||||
0xe7, 0x93, 0xe8, 0xc1, 0xc6, 0x7a, 0x64, 0xe1, 0x4a, 0x6a, 0xe6, 0xb3, 0xf1, 0xd3, 0xa5, 0x8a, 0xb2, 0x9a, 0xba, 0x84, 0xa4, 0x63, 0xe5, 0xc5, 0xf3, 0xd2,
|
||||
0xc4, 0x82, 0xaa, 0xda, 0xe4, 0xf2, 0xca, 0x83, 0xa3, 0xa2, 0xc3, 0xea, 0xc2, 0xe2, 0xe3, 0xff, 0xff,
|
||||
],
|
||||
[
|
||||
0, 2, 2, 1, 4, 1, 4, 1, 3, 3, 1, 0, 0, 0, 0, 140, 0x02, 0x03, 0x01, 0x04, 0x05, 0x12, 0x11, 0x06, 0x13, 0x07, 0x08, 0x14, 0x22, 0x09, 0x21, 0x00, 0x23,
|
||||
0x15, 0x31, 0x32, 0x0a, 0x16, 0xf0, 0x24, 0x33, 0x41, 0x42, 0x19, 0x17, 0x25, 0x18, 0x51, 0x34, 0x43, 0x52, 0x29, 0x35, 0x61, 0x39, 0x71, 0x62, 0x36, 0x53,
|
||||
0x26, 0x38, 0x1a, 0x37, 0x81, 0x27, 0x91, 0x79, 0x55, 0x45, 0x28, 0x72, 0x59, 0xa1, 0xb1, 0x44, 0x69, 0x54, 0x58, 0xd1, 0xfa, 0x57, 0xe1, 0xf1, 0xb9, 0x49,
|
||||
0x47, 0x63, 0x6a, 0xf9, 0x56, 0x46, 0xa8, 0x2a, 0x4a, 0x78, 0x99, 0x3a, 0x75, 0x74, 0x86, 0x65, 0xc1, 0x76, 0xb6, 0x96, 0xd6, 0x89, 0x85, 0xc9, 0xf5, 0x95,
|
||||
0xb4, 0xc7, 0xf7, 0x8a, 0x97, 0xb8, 0x73, 0xb7, 0xd8, 0xd9, 0x87, 0xa7, 0x7a, 0x48, 0x82, 0x84, 0xea, 0xf4, 0xa6, 0xc5, 0x5a, 0x94, 0xa4, 0xc6, 0x92, 0xc3,
|
||||
0x68, 0xb5, 0xc8, 0xe4, 0xe5, 0xe6, 0xe9, 0xa2, 0xa3, 0xe3, 0xc2, 0x66, 0x67, 0x93, 0xaa, 0xd4, 0xd5, 0xe7, 0xf8, 0x88, 0x9a, 0xd7, 0x77, 0xc4, 0x64, 0xe2,
|
||||
0x98, 0xa5, 0xca, 0xda, 0xe8, 0xf3, 0xf6, 0xa9, 0xb2, 0xb3, 0xf2, 0xd2, 0x83, 0xba, 0xd3, 0xff, 0xff,
|
||||
],
|
||||
[
|
||||
0, 0, 6, 2, 1, 3, 3, 2, 5, 1, 2, 2, 8, 10, 0, 117, 0x04, 0x05, 0x03, 0x06, 0x02, 0x07, 0x01, 0x08, 0x09, 0x12, 0x13, 0x14, 0x11, 0x15, 0x0a, 0x16, 0x17,
|
||||
0xf0, 0x00, 0x22, 0x21, 0x18, 0x23, 0x19, 0x24, 0x32, 0x31, 0x25, 0x33, 0x38, 0x37, 0x34, 0x35, 0x36, 0x39, 0x79, 0x57, 0x58, 0x59, 0x28, 0x56, 0x78, 0x27,
|
||||
0x41, 0x29, 0x77, 0x26, 0x42, 0x76, 0x99, 0x1a, 0x55, 0x98, 0x97, 0xf9, 0x48, 0x54, 0x96, 0x89, 0x47, 0xb7, 0x49, 0xfa, 0x75, 0x68, 0xb6, 0x67, 0x69, 0xb9,
|
||||
0xb8, 0xd8, 0x52, 0xd7, 0x88, 0xb5, 0x74, 0x51, 0x46, 0xd9, 0xf8, 0x3a, 0xd6, 0x87, 0x45, 0x7a, 0x95, 0xd5, 0xf6, 0x86, 0xb4, 0xa9, 0x94, 0x53, 0x2a, 0xa8,
|
||||
0x43, 0xf5, 0xf7, 0xd4, 0x66, 0xa7, 0x5a, 0x44, 0x8a, 0xc9, 0xe8, 0xc8, 0xe7, 0x9a, 0x6a, 0x73, 0x4a, 0x61, 0xc7, 0xf4, 0xc6, 0x65, 0xe9, 0x72, 0xe6, 0x71,
|
||||
0x91, 0x93, 0xa6, 0xda, 0x92, 0x85, 0x62, 0xf3, 0xc5, 0xb2, 0xa4, 0x84, 0xba, 0x64, 0xa5, 0xb3, 0xd2, 0x81, 0xe5, 0xd3, 0xaa, 0xc4, 0xca, 0xf2, 0xb1, 0xe4,
|
||||
0xd1, 0x83, 0x63, 0xea, 0xc3, 0xe2, 0x82, 0xf1, 0xa3, 0xc2, 0xa1, 0xc1, 0xe3, 0xa2, 0xe1, 0xff, 0xff,
|
||||
],
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CrwDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
ciff: CiffIFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> CrwDecoder<'a> {
|
||||
pub fn new(file: &RawSource, rawloader: &'a RawLoader) -> Result<CrwDecoder<'a>> {
|
||||
let ciff = CiffIFD::new_file(file)?;
|
||||
|
||||
let makemodel = fetch_ciff_tag!(ciff, CiffTag::MakeModel).get_strings();
|
||||
if makemodel.len() < 2 {
|
||||
return Err(RawlerError::DecoderFailed("CRW: MakeModel tag needs to have 2 strings".to_string()));
|
||||
}
|
||||
let camera = rawloader.check_supported_with_everything(&makemodel[0], &makemodel[1], "")?;
|
||||
|
||||
Ok(CrwDecoder { ciff, rawloader, camera })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for CrwDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let image = if self.camera.model == "Canon PowerShot Pro70" {
|
||||
let src = file.subview_until_eof(26)?;
|
||||
decode_10le_lsb16(src, 1552, 1024, dummy)
|
||||
} else {
|
||||
let sensorinfo = fetch_ciff_tag!(self.ciff, CiffTag::SensorInfo);
|
||||
let width = sensorinfo.get_usize(1);
|
||||
let height = sensorinfo.get_usize(2);
|
||||
self.decode_compressed(file, width, height, dummy)?
|
||||
};
|
||||
|
||||
let wb = {
|
||||
let wb_raw = self.get_wb()?;
|
||||
match self.camera.cfa.unique_colors() {
|
||||
3 => normalize_wb(wb_raw),
|
||||
4 => {
|
||||
let maxval = wb_raw.into_iter().map(|v| v as i32).min().unwrap_or(0) as f32;
|
||||
[wb_raw[0] / maxval, wb_raw[1] / maxval, wb_raw[2] / maxval, wb_raw[3] / maxval]
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, wb, image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, __params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
// TODO: Add EXIF info
|
||||
let exif = Exif::default();
|
||||
Ok(RawMetadata::new(&self.camera, exif))
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::CRW
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CrwDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
if let Some(levels) = self.ciff.find_entry(CiffTag::WhiteBalance) {
|
||||
let offset = self.camera.param_usize("wb_offset").unwrap_or(0);
|
||||
return Ok([
|
||||
levels.get_f32(offset + 0),
|
||||
levels.get_f32(offset + 1),
|
||||
levels.get_f32(offset + 2),
|
||||
levels.get_f32(offset + 3),
|
||||
]);
|
||||
}
|
||||
if !self.camera.find_hint("nocinfo2") {
|
||||
if let Some(cinfo) = self.ciff.find_entry(CiffTag::ColorInfo2) {
|
||||
return Ok(if cinfo.get_u32(0) > 512 {
|
||||
[cinfo.get_f32(62), cinfo.get_f32(63), cinfo.get_f32(60), cinfo.get_f32(61)]
|
||||
// RGBE???
|
||||
} else {
|
||||
[cinfo.get_f32(51), cinfo.get_f32(50), cinfo.get_f32(53), cinfo.get_f32(52)]
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(cinfo) = self.ciff.find_entry(CiffTag::ColorInfo1) {
|
||||
if cinfo.count == 768 {
|
||||
// D30
|
||||
return Ok([
|
||||
1024.0 / (cinfo.get_force_u16(36) as f32),
|
||||
1024.0 / (cinfo.get_force_u16(37) as f32),
|
||||
1024.0 / (cinfo.get_force_u16(38) as f32),
|
||||
1024.0 / (cinfo.get_force_u16(39) as f32),
|
||||
]);
|
||||
}
|
||||
let off = self.camera.param_usize("wb_offset").unwrap_or(0);
|
||||
let key: [u16; 2] = if self.camera.find_hint("wb_mangle") { [0x410, 0x45f3] } else { [0, 0] };
|
||||
return Ok([
|
||||
(cinfo.get_force_u16(off + 1) ^ key[1]) as f32,
|
||||
(cinfo.get_force_u16(off + 0) ^ key[0]) as f32,
|
||||
(cinfo.get_force_u16(off + 0) ^ key[0]) as f32,
|
||||
(cinfo.get_force_u16(off + 2) ^ key[0]) as f32,
|
||||
]);
|
||||
}
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
|
||||
fn create_hufftables(num: usize) -> [HuffTable; 2] {
|
||||
[Self::create_hufftable(&CRW_FIRST_TREE[num]), Self::create_hufftable(&CRW_SECOND_TREE[num])]
|
||||
}
|
||||
|
||||
fn create_hufftable(table: &[u8]) -> HuffTable {
|
||||
let mut htable = HuffTable::empty();
|
||||
|
||||
for i in 0..16 {
|
||||
htable.bits[i + 1] = table[i] as u32;
|
||||
}
|
||||
for i in 16..table.len() {
|
||||
htable.huffval[i - 16] = table[i] as u32;
|
||||
}
|
||||
|
||||
htable.disable_cache = true;
|
||||
htable.initialize().unwrap();
|
||||
htable
|
||||
}
|
||||
|
||||
fn decode_compressed(&self, file: &RawSource, width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let lowbits = !self.camera.find_hint("nolowbits");
|
||||
let dectable = fetch_ciff_tag!(self.ciff, CiffTag::DecoderTable).get_usize(0);
|
||||
if dectable > 2 {
|
||||
return Err(RawlerError::DecoderFailed(format!("CRW: Unknown decoder table {}", dectable)));
|
||||
}
|
||||
Self::do_decode(file, lowbits, dectable, width, height, dummy)
|
||||
}
|
||||
|
||||
pub(crate) fn do_decode(file: &RawSource, lowbits: bool, dectable: usize, width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
|
||||
let htables = Self::create_hufftables(dectable);
|
||||
let offset = 540 + (lowbits as usize) * height * width / 4;
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
let mut pump = BitPumpJPEG::new(src);
|
||||
|
||||
let mut carry: i32 = 0;
|
||||
let mut base = [0_i32; 2];
|
||||
let mut pnum = 0;
|
||||
for pixout in out.pixels_mut().chunks_exact_mut(64) {
|
||||
// Decode a block of 64 differences
|
||||
let mut diffbuf = [0_i32; 64];
|
||||
let mut i: usize = 0;
|
||||
while i < 64 {
|
||||
let tbl = &htables[(i > 0) as usize];
|
||||
let leaf = tbl.huff_get_bits(&mut pump);
|
||||
if leaf == 0 && i != 0 {
|
||||
break;
|
||||
}
|
||||
if leaf == 0xff {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
i += (leaf >> 4) as usize;
|
||||
let len = leaf & 0x0f;
|
||||
if len == 0 {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let mut diff: i32 = pump.get_bits(len) as i32;
|
||||
if (diff & (1 << (len - 1))) == 0 {
|
||||
diff -= (1 << len) - 1;
|
||||
}
|
||||
if i < 64 {
|
||||
diffbuf[i] = diff;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
diffbuf[0] += carry;
|
||||
carry = diffbuf[0];
|
||||
|
||||
// Save those differences to 64 pixels adjusting the predictor as we go
|
||||
for i in 0..64 {
|
||||
// At the start of lines reset the predictor to 512
|
||||
if pnum % width == 0 {
|
||||
base[0] = 512;
|
||||
base[1] = 512;
|
||||
}
|
||||
pnum += 1;
|
||||
base[i & 1] += diffbuf[i];
|
||||
pixout[i] = base[i & 1] as u16;
|
||||
}
|
||||
}
|
||||
|
||||
if lowbits {
|
||||
let buffer = file.as_vec()?;
|
||||
// Add the uncompressed 2 low bits to the decoded 8 high bits
|
||||
for (i, o) in out.pixels_mut().chunks_exact_mut(4).enumerate() {
|
||||
let c = buffer[26 + i] as u16;
|
||||
o[0] = (o[0] << 2) | (c) & 0x03;
|
||||
o[1] = (o[1] << 2) | (c >> 2) & 0x03;
|
||||
o[2] = (o[2] << 2) | (c >> 4) & 0x03;
|
||||
o[3] = (o[3] << 2) | (c >> 6) & 0x03;
|
||||
if width == 2672 {
|
||||
// No idea why this is needed, probably some broken camera
|
||||
if o[0] < 512 {
|
||||
o[0] += 2
|
||||
}
|
||||
if o[1] < 512 {
|
||||
o[1] += 2
|
||||
}
|
||||
if o[2] < 512 {
|
||||
o[2] += 2
|
||||
}
|
||||
if o[3] < 512 {
|
||||
o[3] += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
debug!("CRW raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1];
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
use std::cmp;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::BEu16;
|
||||
use crate::bits::Endian;
|
||||
use crate::bits::LookupTable;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::pumps::ByteStream;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
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 DcrDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> DcrDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<DcrDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let kodak_ifd = fetch_tiff_tag!(tiff, TiffCommonTag::KodakIFD);
|
||||
let makernote = IFD::new(&mut file.reader(), kodak_ifd.force_u32(0), 0, 0, tiff.get_endian(), &[])?;
|
||||
|
||||
Ok(DcrDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
makernote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for DcrDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::CFAPattern)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with CFAPattern tag")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
|
||||
let src = file.subview_until_eof_padded(offset as u64)?; // TODO add size and check all samples
|
||||
|
||||
let linearization = fetch_tiff_tag!(self.makernote, TiffCommonTag::DcrLinearization);
|
||||
let curve = {
|
||||
let mut points = Vec::new();
|
||||
for i in 0..linearization.count() {
|
||||
points.push(linearization.force_u32(i) as u16);
|
||||
}
|
||||
LookupTable::new(&points)
|
||||
};
|
||||
|
||||
let image = DcrDecoder::decode_kodak65000(&src, &curve, width, height, dummy);
|
||||
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, self.get_wb()?, image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, __params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::DCR
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DcrDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let dcrwb = fetch_tiff_tag!(self.makernote, TiffCommonTag::DcrWB);
|
||||
if dcrwb.count() >= 46 {
|
||||
let levels = dcrwb.get_data();
|
||||
Ok([
|
||||
2048.0 / BEu16(levels, 40) as f32,
|
||||
2048.0 / BEu16(levels, 42) as f32,
|
||||
2048.0 / BEu16(levels, 44) as f32,
|
||||
f32::NAN,
|
||||
])
|
||||
} else {
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode_kodak65000(buf: &[u8], curve: &LookupTable, width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
let mut input = ByteStream::new(buf, Endian::Little);
|
||||
|
||||
let mut random: u32 = 0;
|
||||
for row in 0..height {
|
||||
for col in (0..width).step_by(256) {
|
||||
let mut pred: [i32; 2] = [0; 2];
|
||||
let buf = DcrDecoder::decode_segment(&mut input, cmp::min(256, width - col));
|
||||
for (i, val) in buf.iter().enumerate() {
|
||||
pred[i & 1] += *val;
|
||||
if pred[i & 1] < 0 {
|
||||
panic!("Found a negative pixel!");
|
||||
}
|
||||
out[row * width + col + i] = curve.dither(pred[i & 1] as u16, &mut random);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn decode_segment(input: &mut ByteStream, size: usize) -> Vec<i32> {
|
||||
let mut out: Vec<i32> = vec![0; size];
|
||||
|
||||
let mut lens: [usize; 256] = [0; 256];
|
||||
for i in (0..size).step_by(2) {
|
||||
lens[i] = (input.peek_u8() & 15) as usize;
|
||||
lens[i + 1] = (input.get_u8() >> 4) as usize;
|
||||
}
|
||||
|
||||
let mut bitbuf: u64 = 0;
|
||||
let mut bits: usize = 0;
|
||||
if (size & 7) == 4 {
|
||||
bitbuf = ((input.get_u8() as u64) << 8) | (input.get_u8() as u64);
|
||||
bits = 16;
|
||||
}
|
||||
|
||||
for i in 0..size {
|
||||
let len = lens[i];
|
||||
if bits < len {
|
||||
for j in (0..32).step_by(8) {
|
||||
bitbuf += (input.get_u8() as u64) << (bits + (j ^ 8));
|
||||
}
|
||||
bits += 32;
|
||||
}
|
||||
out[i] = (bitbuf & (0xffff >> (16 - len))) as i32;
|
||||
bitbuf >>= len;
|
||||
bits -= len;
|
||||
if len != 0 && (out[i] & (1 << (len - 1))) == 0 {
|
||||
out[i] -= (1 << len) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::LookupTable;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::packed::decode_8bit_wtable;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
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 DcsDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> DcsDecoder<'a> {
|
||||
pub fn new(_file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<DcsDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
Ok(DcsDecoder { camera, tiff, rawloader })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for DcsDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_ifd_with_new_subfile_type(0)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find IFD with subfile type 0")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let src = file.subview_until_eof_padded(offset as u64)?; // TODO add size and check all samples
|
||||
|
||||
let linearization = fetch_tiff_tag!(self.tiff, TiffCommonTag::GrayResponse);
|
||||
let table = {
|
||||
let mut t: [u16; 256] = [0; 256];
|
||||
for i in 0..256 {
|
||||
t[i] = linearization.force_u32(i) as u16;
|
||||
}
|
||||
LookupTable::new(&t)
|
||||
};
|
||||
|
||||
let image = decode_8bit_wtable(&src, &table, width, height, dummy);
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, [f32::NAN, f32::NAN, f32::NAN, f32::NAN], image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::DCS
|
||||
}
|
||||
}
|
||||
+436
@@ -0,0 +1,436 @@
|
||||
use crate::RawImage;
|
||||
use crate::cfa::*;
|
||||
use crate::decoders::*;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::Rational;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::imgop::matrix::*;
|
||||
use crate::imgop::xyz::*;
|
||||
use crate::tags::DngTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DngDecoder<'a> {
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl<'a> DngDecoder<'a> {
|
||||
pub fn new(_file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<DngDecoder<'a>> {
|
||||
Ok(DngDecoder { tiff, rawloader })
|
||||
}
|
||||
}
|
||||
|
||||
/// DNG format encapsulation for analyzer
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DngFormat {
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl<'a> Decoder for DngDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self.get_raw_ifd()?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let cpp = fetch_tiff_tag!(raw, TiffCommonTag::SamplesPerPixel).force_usize(0);
|
||||
let bits = fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).force_u32(0);
|
||||
let orientation = Orientation::from_tiff(self.tiff.root_ifd());
|
||||
|
||||
let mut cam = self.make_camera(raw, width, height)?;
|
||||
// If we know the camera, re-use the clean names
|
||||
if let Ok(known_cam) = self
|
||||
.rawloader
|
||||
.check_supported_with_mode(self.tiff.root_ifd(), "dng")
|
||||
.or_else(|_| self.rawloader.check_supported(self.tiff.root_ifd()))
|
||||
{
|
||||
cam.clean_make = known_cam.clean_make;
|
||||
cam.clean_model = known_cam.clean_model;
|
||||
cam.hints.extend_from_slice(&known_cam.hints);
|
||||
cam.params.extend(known_cam.params.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||
} else {
|
||||
log::debug!("DNG: camera {} / {} is not in the camera catalog", cam.make, cam.model);
|
||||
// panic!("Camera {} / {} is not in the camera catalog", cam.make, cam.model);
|
||||
}
|
||||
|
||||
let blacklevel = self.get_blacklevels(raw)?;
|
||||
let whitelevel = self.get_whitelevels(raw)?.or(Some(WhiteLevel::new_bits(bits, cpp)));
|
||||
|
||||
let photometric = match fetch_tiff_tag!(raw, TiffCommonTag::PhotometricInt).force_u32(0) {
|
||||
1 => RawPhotometricInterpretation::BlackIsZero,
|
||||
32803 => RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&cam)),
|
||||
34892 => RawPhotometricInterpretation::LinearRaw,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let raw_data = plain_image_from_ifd(raw, file)?;
|
||||
let wb_coeffs = self.get_wb(&cam)?;
|
||||
let mut image = RawImage::new_with_data(cam, raw_data, width * cpp, height, cpp, wb_coeffs, photometric, blacklevel, whitelevel, dummy);
|
||||
image.orientation = orientation;
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
FormatDump::Dng(DngFormat { tiff: self.tiff.clone() })
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let raw = self.get_raw_ifd()?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let mut cam = self.make_camera(raw, width, height)?;
|
||||
// If we know the camera, re-use the clean names
|
||||
if let Ok(known_cam) = self.rawloader.check_supported(self.tiff.root_ifd()) {
|
||||
cam.clean_make = known_cam.clean_make;
|
||||
cam.clean_model = known_cam.clean_model;
|
||||
}
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&cam, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn thumbnail_image(&self, file: &RawSource, _params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if let Some(thumb_ifd) = Some(self.tiff.root_ifd()).filter(|ifd| ifd.get_entry(TiffCommonTag::NewSubFileType).map(|entry| entry.force_u16(0)) == Some(1)) {
|
||||
Ok(Some(dynamic_image_from_ifd(thumb_ifd, file)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(sub_ifds) = self.tiff.root_ifd().get_sub_ifd_all(TiffCommonTag::SubIFDs) {
|
||||
let first_ifd = sub_ifds
|
||||
.iter()
|
||||
.find(|ifd| ifd.get_entry(TiffCommonTag::NewSubFileType).map(|entry| entry.force_u32(0)) == Some(1));
|
||||
if let Some(preview_ifd) = first_ifd {
|
||||
return Ok(Some(dynamic_image_from_ifd(preview_ifd, file)?));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn ifd(&self, wk_ifd: WellKnownIFD) -> Result<Option<Rc<IFD>>> {
|
||||
Ok(match wk_ifd {
|
||||
WellKnownIFD::Root => Some(Rc::new(self.tiff.root_ifd().clone())),
|
||||
WellKnownIFD::Raw => Some(Rc::new(self.get_raw_ifd()?.clone())),
|
||||
WellKnownIFD::Exif => self
|
||||
.tiff
|
||||
.root_ifd()
|
||||
.get_sub_ifd_all(ExifTag::ExifOffset)
|
||||
.and_then(|list| list.get(0))
|
||||
.cloned()
|
||||
.map(Rc::new),
|
||||
WellKnownIFD::ExifGps => self
|
||||
.tiff
|
||||
.root_ifd()
|
||||
.get_sub_ifd_all(ExifTag::GPSInfo)
|
||||
.and_then(|list| list.get(0))
|
||||
.cloned()
|
||||
.map(Rc::new),
|
||||
WellKnownIFD::VirtualDngRawTags => {
|
||||
let mut ifd = IFD::default();
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::OpcodeList1);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::OpcodeList2);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::OpcodeList3);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::NoiseProfile);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::BayerGreenSplit);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::ChromaBlurRadius);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::AntiAliasStrength);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::NoiseReductionApplied);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::ProfileGainTableMap);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::CameraCalibration1);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::CameraCalibration2);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::CameraCalibration3);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::ForwardMatrix1);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::ForwardMatrix2);
|
||||
IFD::copy_tag(&mut ifd, self.get_raw_ifd()?, DngTag::ForwardMatrix3);
|
||||
Some(Rc::new(ifd))
|
||||
}
|
||||
WellKnownIFD::VirtualDngRootTags => {
|
||||
let mut ifd = IFD::default();
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileEmbedPolicy);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapData1);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapData2);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapData3);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapDims);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapData1);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapData2);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapData3);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileHueSatMapEncoding);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileLookTableData);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileLookTableDims);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileLookTableEncoding);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileName);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileCopyright);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::ProfileToneCurve);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DNGPrivateData);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::MakerNoteSafety);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::AnalogBalance);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::BaselineExposure);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::BaselineNoise);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::BaselineSharpness);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::LinearResponseLimit);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::CameraSerialNumber);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::AsShotICCProfile);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::AsShotPreProfileMatrix);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::CurrentICCProfile);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::CurrentPreProfileMatrix);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::AsShotProfileName);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DefaultBlackRender);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::BaselineExposureOffset);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DepthFormat);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DepthNear);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DepthFar);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DepthUnits);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::DepthMeasureType);
|
||||
IFD::copy_tag(&mut ifd, self.tiff.root_ifd(), DngTag::RGBTables);
|
||||
Some(Rc::new(ifd))
|
||||
}
|
||||
_ => return Ok(None),
|
||||
})
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::DNG
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DngDecoder<'a> {
|
||||
fn get_raw_ifd(&self) -> Result<&IFD> {
|
||||
let ifds = self
|
||||
.tiff
|
||||
.find_ifds_with_tag(TiffCommonTag::Compression)
|
||||
.into_iter()
|
||||
.filter(|ifd| {
|
||||
let compression = (**ifd)
|
||||
.get_entry(TiffCommonTag::Compression)
|
||||
.expect("This IFD must contains this tag")
|
||||
.force_u32(0);
|
||||
let subsampled = match (**ifd).get_entry(TiffCommonTag::NewSubFileType) {
|
||||
Some(e) => e.force_u32(0) & 1 != 0,
|
||||
None => false,
|
||||
};
|
||||
!subsampled && (compression == 7 || compression == 8 || compression == 1 || compression == 0x884c || compression == 52546)
|
||||
})
|
||||
.collect::<Vec<&IFD>>();
|
||||
if let Some(first) = ifds.first() {
|
||||
Ok(first)
|
||||
} else {
|
||||
Err(RawlerError::DecoderFailed(format!("TODO: Unsupported DNG compression")))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_camera(&self, raw: &IFD, width: usize, height: usize) -> Result<Camera> {
|
||||
let mode = String::from("dng");
|
||||
|
||||
let make = self
|
||||
.tiff
|
||||
.root_ifd()
|
||||
.get_entry(TiffCommonTag::Make)
|
||||
.and_then(|x| x.as_string())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let model = self
|
||||
.tiff
|
||||
.root_ifd()
|
||||
.get_entry(TiffCommonTag::Model)
|
||||
.and_then(|x| x.as_string())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let active_area = self.get_active_area(raw, width, height);
|
||||
let crop_area = if let Some(crops) = self.get_crop(raw) {
|
||||
if let Some(active_area) = &active_area {
|
||||
let mut full = crops;
|
||||
full.p.x += active_area[0]; // left
|
||||
full.p.y += active_area[1]; // Top
|
||||
Some(full.as_ltrb_offsets(width, height))
|
||||
} else {
|
||||
Some(crops.as_ltrb_offsets(width, height))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let linear = fetch_tiff_tag!(raw, TiffCommonTag::PhotometricInt).force_usize(0) == 34892;
|
||||
let cfa = if linear { CFA::default() } else { self.get_cfa(raw)? };
|
||||
let color_matrix = self.get_color_matrix()?;
|
||||
let real_bps = if raw.has_entry(TiffCommonTag::Linearization) {
|
||||
// If DNG contains linearization table, output is always 16 bits
|
||||
16
|
||||
} else {
|
||||
raw.get_entry(TiffCommonTag::BitsPerSample).map(|v| v.force_usize(0)).unwrap_or(16)
|
||||
};
|
||||
|
||||
Ok(Camera {
|
||||
clean_make: make.clone(),
|
||||
clean_model: model.clone(),
|
||||
make,
|
||||
model,
|
||||
mode,
|
||||
whitelevel: None,
|
||||
blacklevel: None,
|
||||
blackareah: None,
|
||||
blackareav: None,
|
||||
xyz_to_cam: Default::default(),
|
||||
color_matrix,
|
||||
cfa,
|
||||
active_area,
|
||||
crop_area,
|
||||
real_bps,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_wb(&self, cam: &Camera) -> Result<[f32; 4]> {
|
||||
if let Some(levels) = self.tiff.get_entry(DngTag::AsShotNeutral) {
|
||||
Ok([1.0 / levels.force_f32(0), 1.0 / levels.force_f32(1), 1.0 / levels.force_f32(2), f32::NAN])
|
||||
} else if let Some(levels) = self.tiff.get_entry(DngTag::AsShotWhiteXY) {
|
||||
// TODO: improve once AnalogBalance and CC is properly implemented
|
||||
if let Some(flat_colormatrix) = cam.color_matrix.get(&Illuminant::D65)
|
||||
&& let Some(colormatrix) = transform_1d::<3, 3>(flat_colormatrix)
|
||||
{
|
||||
let wb_coeff = xy_whitepoint_to_wb_coeff(levels.force_f32(0), levels.force_f32(1), &colormatrix);
|
||||
Ok([wb_coeff[0], wb_coeff[1], wb_coeff[2], f32::NAN])
|
||||
} else {
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
} else {
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
}
|
||||
|
||||
fn get_blacklevels(&self, raw: &IFD) -> Result<Option<BlackLevel>> {
|
||||
let cpp = raw.get_entry(TiffCommonTag::SamplesPerPixel).map(|entry| entry.force_usize(0)).unwrap_or(1);
|
||||
if let Some(entry) = raw.get_entry(TiffCommonTag::BlackLevels) {
|
||||
let levels = match &entry.value {
|
||||
Value::Short(black) => black.iter().copied().map(Rational::from).collect(),
|
||||
Value::Long(black) => black.iter().copied().map(Rational::from).collect(),
|
||||
Value::Rational(black) => black.clone(),
|
||||
_ => return Err(format!("Unsupported BlackLevel type: {}", entry.value_type_name()).into()),
|
||||
};
|
||||
let mut repeat = (1, 1);
|
||||
if let Some(Entry {
|
||||
value: Value::Short(value), ..
|
||||
}) = raw.get_entry(DngTag::BlackLevelRepeatDim)
|
||||
{
|
||||
if value.len() == 2 {
|
||||
repeat = (value[0] as usize, value[1] as usize);
|
||||
} else {
|
||||
// Pentax K-3 Mark III Monochrome is known to has invalid tag
|
||||
log::warn!("File has BlackLevelRepeatDim tag but with invalid length: {}", value.len());
|
||||
}
|
||||
}
|
||||
Ok(Some(BlackLevel::new(&levels, repeat.1, repeat.0, cpp)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_whitelevels(&self, raw: &IFD) -> Result<Option<WhiteLevel>> {
|
||||
let cpp = fetch_tiff_tag!(raw, TiffCommonTag::SamplesPerPixel).force_usize(0);
|
||||
if let Some(levels) = raw.get_entry(TiffCommonTag::WhiteLevel) {
|
||||
let mut whitelevels = WhiteLevel((0..levels.count()).map(|i| levels.force_u32(i as usize)).collect());
|
||||
// Fixes a bug where only a single whitelevel value is given.
|
||||
if whitelevels.0.len() == 1 && cpp > 1 {
|
||||
whitelevels.0 = vec![whitelevels.0[0]; cpp];
|
||||
}
|
||||
return Ok(Some(whitelevels));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_cfa(&self, raw: &IFD) -> Result<CFA> {
|
||||
let pattern = fetch_tiff_tag!(raw, TiffCommonTag::CFAPattern);
|
||||
let cfa = CFA::new_from_tag(pattern);
|
||||
// If DNG has active area, we need to calulate back the CFA pattern,
|
||||
// because for DNG the CFAPattern is relative to ActiveArea and we
|
||||
// use (0, 0) as starting point.
|
||||
if let Some(active_area) = self.get_active_area_borders(raw) {
|
||||
let top = active_area[0];
|
||||
let left = active_area[1];
|
||||
Ok(cfa.shift(left % cfa.width, top % cfa.height))
|
||||
} else {
|
||||
Ok(cfa)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_active_area_borders(&self, raw: &IFD) -> Option<[usize; 4]> {
|
||||
if let Some(crops) = raw.get_entry(DngTag::ActiveArea) {
|
||||
let rect = [crops.force_usize(0), crops.force_usize(1), crops.force_usize(2), crops.force_usize(3)];
|
||||
Some(rect)
|
||||
} else {
|
||||
// Ignore missing crops, at least some pentax DNGs don't have it
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_active_area(&self, raw: &IFD, width: usize, height: usize) -> Option<[usize; 4]> {
|
||||
if let Some(rect) = self.get_active_area_borders(raw) {
|
||||
Some(Rect::new_with_dng(&rect).as_ltrb_offsets(width, height))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_crop(&self, raw: &IFD) -> Option<Rect> {
|
||||
if let Some(crops) = raw.get_entry(DngTag::DefaultCropOrigin) {
|
||||
let p = Point::new(crops.force_usize(0), crops.force_usize(1));
|
||||
if let Some(size) = raw.get_entry(DngTag::DefaultCropSize) {
|
||||
let d = Dim2::new(size.force_usize(0), size.force_usize(1));
|
||||
return Some(Rect::new(p, d));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn _get_masked_areas(&self, raw: &IFD) -> Vec<Rect> {
|
||||
let mut areas = Vec::new();
|
||||
|
||||
if let Some(masked_area) = raw.get_entry(TiffCommonTag::MaskedAreas) {
|
||||
for x in (0..masked_area.count() as usize).step_by(4) {
|
||||
areas.push(Rect::new_with_points(
|
||||
Point::new(masked_area.force_usize(x), masked_area.force_usize(x + 1)),
|
||||
Point::new(masked_area.force_usize(x + 2), masked_area.force_usize(x + 3)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
areas
|
||||
}
|
||||
|
||||
fn get_color_matrix(&self) -> Result<HashMap<Illuminant, FlatColorMatrix>> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
let mut read_matrix = |cal: DngTag, mat: DngTag| -> Result<()> {
|
||||
if let Some(c) = self.tiff.get_entry(mat) {
|
||||
let illuminant_val = self.tiff.get_entry(cal).map(|e| e.force_u16(0)).unwrap_or(21);
|
||||
let illuminant: Illuminant = illuminant_val.try_into().unwrap_or(Illuminant::D65);
|
||||
|
||||
let mut matrix = FlatColorMatrix::new();
|
||||
for i in 0..c.count() as usize {
|
||||
matrix.push(c.force_f32(i));
|
||||
}
|
||||
|
||||
if !matrix.is_empty() && matrix.len() <= 12 {
|
||||
result.insert(illuminant, matrix);
|
||||
} else {
|
||||
log::warn!("Invalid ColorMatrix dimensions for illuminant {:?}: length {}", illuminant, matrix.len());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
read_matrix(DngTag::CalibrationIlluminant1, DngTag::ColorMatrix1)?;
|
||||
read_matrix(DngTag::CalibrationIlluminant2, DngTag::ColorMatrix2)?;
|
||||
// TODO: add 3
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
use log::warn;
|
||||
|
||||
use crate::RawlerError;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::rawsource::RawSource;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::Result;
|
||||
use crate::bits::BEu16;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::packed::decode_12be_wcontrol;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::CFAConfig;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::RawPhotometricInterpretation;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ErfDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> ErfDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<ErfDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::Absolute, &[])?
|
||||
} else {
|
||||
warn!("ERF makernote not found");
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
|
||||
Ok(ErfDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
makernote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for ErfDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::CFAPattern)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with CFAPattern tag")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
|
||||
let image = decode_12be_wcontrol(src, width, height, dummy);
|
||||
let cpp = 1;
|
||||
|
||||
let blacklevel = self.get_blacklevel(cpp);
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera));
|
||||
let img = RawImage::new(self.camera.clone(), image, cpp, self.get_wb()?, photometric, blacklevel, None, dummy);
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::ERF
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ErfDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let levels = fetch_tiff_tag!(self.makernote, TiffCommonTag::EpsonWB);
|
||||
if levels.count() != 256 {
|
||||
Err(RawlerError::DecoderFailed("ERF: Levels count is off".to_string()))
|
||||
} else {
|
||||
let r = BEu16(levels.get_data(), 48) as f32;
|
||||
let b = BEu16(levels.get_data(), 50) as f32;
|
||||
Ok([r * 508.0 * 1.078 / 65536.0, 1.0, b * 382.0 * 1.173 / 65536.0, f32::NAN])
|
||||
}
|
||||
}
|
||||
|
||||
fn get_blacklevel(&self, cpp: usize) -> Option<BlackLevel> {
|
||||
if let Some(levels) = self.makernote.get_entry(0x0401) {
|
||||
let levels = [levels.force_u16(0), levels.force_u16(1), levels.force_u16(2), levels.force_u16(3)];
|
||||
return Some(BlackLevel::new(&levels, self.camera.cfa.width, self.camera.cfa.height, cpp));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
+1196
File diff suppressed because it is too large
Load Diff
+219
@@ -0,0 +1,219 @@
|
||||
use log::warn;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawSource;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image;
|
||||
use crate::alloc_image_ok;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::BEu16;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::packed::decode_12be;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::CFAConfig;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::RawPhotometricInterpretation;
|
||||
use super::WhiteLevel;
|
||||
use super::ok_cfa_image;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KdcDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: Option<IFD>,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> KdcDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<KdcDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::Absolute, &[])?
|
||||
} else {
|
||||
warn!("KDC makernote not found");
|
||||
None
|
||||
};
|
||||
|
||||
Ok(KdcDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
makernote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for KdcDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
if self.camera.clean_model == "DC120" {
|
||||
let width = 848;
|
||||
let height = 976;
|
||||
let raw = self.tiff.find_ifds_with_tag(TiffCommonTag::CFAPattern)[0];
|
||||
let off = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let mut white = self.camera.whitelevel.clone().expect("KDC needs a whitelevel in camera config")[0];
|
||||
let src = file.subview_until_eof(off as u64)?;
|
||||
let image = match fetch_tiff_tag!(raw, TiffCommonTag::Compression).force_usize(0) {
|
||||
1 => Self::decode_dc120(src, width, height, dummy),
|
||||
7 => {
|
||||
white = 0xFF << 1;
|
||||
Self::decode_dc120_jpeg(src, width, height, dummy)?
|
||||
}
|
||||
c => {
|
||||
return Err(RawlerError::unsupported(
|
||||
&self.camera,
|
||||
format!("KDC: DC120: Don't know how to handle compression type {}", c),
|
||||
));
|
||||
}
|
||||
};
|
||||
let cpp = 1;
|
||||
let whitelevel = Some(WhiteLevel::new(vec![white; cpp]));
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera));
|
||||
let img = RawImage::new(self.camera.clone(), image, cpp, [1.0, 1.0, 1.0, f32::NAN], photometric, None, whitelevel, dummy);
|
||||
return Ok(img);
|
||||
}
|
||||
|
||||
if self.camera.clean_model == "DC50" {
|
||||
let raw = self.tiff.find_ifds_with_tag(TiffCommonTag::CFAPattern)[0];
|
||||
let width = self.camera.raw_width;
|
||||
let height = self.camera.raw_height;
|
||||
let off = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let white = self.camera.whitelevel.clone().expect("KDC needs a whitelevel in camera config")[0];
|
||||
let cbpp = match raw.get_entry(ExifTag::CompressedBitsPerPixel) {
|
||||
Some(Entry {
|
||||
value: Value::Rational(data), ..
|
||||
}) if data[0].n == 243 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
let src = file.subview_until_eof_padded(off as u64)?;
|
||||
let image = crate::decompressors::radc::decompress(&src, width, height, cbpp, dummy)?;
|
||||
let cpp = 1;
|
||||
let whitelevel = Some(WhiteLevel::new(vec![white; cpp]));
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera));
|
||||
let img = RawImage::new(self.camera.clone(), image, cpp, [1.0, 1.0, 1.0, f32::NAN], photometric, None, whitelevel, dummy);
|
||||
return Ok(img);
|
||||
}
|
||||
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::KdcWidth)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with KdcWidth tag")))?;
|
||||
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::KdcWidth).force_usize(0) + 80;
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::KdcLength).force_usize(0) + 70;
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::KdcOffset);
|
||||
if offset.count() < 13 {
|
||||
panic!("KDC Decoder: Couldn't find the KDC offset");
|
||||
}
|
||||
let mut off = offset.force_usize(4) + offset.force_usize(12);
|
||||
|
||||
// Offset hardcoding gotten from dcraw
|
||||
if self.camera.find_hint("easyshare_offset_hack") {
|
||||
off = if off < 0x15000 { 0x15000 } else { 0x17000 };
|
||||
}
|
||||
|
||||
let src = file.subview_until_eof(off as u64)?;
|
||||
let image = decode_12be(src, width, height, dummy);
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, self.get_wb()?, image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::KDC
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> KdcDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
if let Some(makernote) = self.makernote.as_ref() {
|
||||
match makernote.get_entry(TiffCommonTag::KdcWB) {
|
||||
Some(levels) => {
|
||||
if levels.count() != 3 {
|
||||
Err(format!("KDC: Levels count is off: {}", levels.count()).into())
|
||||
} else {
|
||||
Ok([levels.force_f32(0), levels.force_f32(1), levels.force_f32(2), f32::NAN])
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let levels = fetch_tiff_tag!(makernote, TiffCommonTag::KodakWB);
|
||||
if ![734, 1502, 1512, 2288].contains(&levels.count()) {
|
||||
Err(format!("KDC: Levels count is off: {}", levels.count()).into())
|
||||
} else {
|
||||
let r = BEu16(levels.get_data(), 148) as f32;
|
||||
let b = BEu16(levels.get_data(), 150) as f32;
|
||||
Ok([r / 256.0, 1.0, b / 256.0, f32::NAN])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode_dc120(src: &[u8], width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
|
||||
let mul: [usize; 4] = [162, 192, 187, 92];
|
||||
let add: [usize; 4] = [0, 636, 424, 212];
|
||||
for row in 0..height {
|
||||
let shift = row * mul[row & 3] + add[row & 3];
|
||||
for col in 0..width {
|
||||
out[row * width + col] = src[row * width + ((col + shift) % 848)] as u16;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn decode_dc120_jpeg(src: &[u8], width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
|
||||
let swapped_src: Vec<u8> = src.chunks_exact(2).flat_map(|x| [x[1], x[0]]).collect();
|
||||
|
||||
let img = image::load_from_memory_with_format(&swapped_src, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to read JPEG image: {:?}", err)))?;
|
||||
|
||||
assert_eq!(width, img.width() as usize);
|
||||
assert_eq!(height, img.height() as usize * 2);
|
||||
let buf = img.as_flat_samples_u8().unwrap();
|
||||
let jpeg = buf.as_slice();
|
||||
|
||||
for irow in 0..img.height() as usize {
|
||||
let row = irow * 2;
|
||||
let iline = &jpeg[irow * width * 3..];
|
||||
for col in (0..width).step_by(2) {
|
||||
*out.at_mut(row + 0, col + 0) = (iline[col * 3 + 1] as u16) << 1;
|
||||
*out.at_mut(row + 1, col + 1) = (iline[(col + 1) * 3 + 1] as u16) << 1;
|
||||
*out.at_mut(row + 0, col + 1) = (iline[col * 3 + 0]) as u16 + (iline[(col + 1) * 3 + 0]) as u16;
|
||||
*out.at_mut(row + 1, col + 0) = (iline[col * 3 + 2]) as u16 + (iline[(col + 1) * 3 + 2]) as u16;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::packed::decode_12be;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
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 MefDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> MefDecoder<'a> {
|
||||
pub fn new(_file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<MefDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
Ok(MefDecoder { tiff, rawloader, camera })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for MefDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = &self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::CFAPattern)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with CFAPattern tag")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
|
||||
let image = decode_12be(src, width, height, dummy);
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, [f32::NAN, f32::NAN, f32::NAN, f32::NAN], image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, __params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::MEF
|
||||
}
|
||||
}
|
||||
+1144
File diff suppressed because it is too large
Load Diff
+142
@@ -0,0 +1,142 @@
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image_ok;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::decompressors::ljpeg::LjpegDecompressor;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::packed::decode_16be;
|
||||
use crate::packed::decode_16le;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
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 MosDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> MosDecoder<'a> {
|
||||
pub fn new(_file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<MosDecoder<'a>> {
|
||||
let make = Self::xmp_tag(&tiff, "Make")?;
|
||||
let model_full = Self::xmp_tag(&tiff, "Model")?;
|
||||
let model = model_full.split_terminator('(').next().unwrap();
|
||||
let camera = rawloader.check_supported_with_everything(&make, model, "")?;
|
||||
|
||||
Ok(MosDecoder { tiff, rawloader, camera })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for MosDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::TileOffsets)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with TileOffsets tag")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::TileOffsets).force_usize(0);
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
|
||||
let image = match fetch_tiff_tag!(raw, TiffCommonTag::Compression).force_usize(0) {
|
||||
1 => {
|
||||
if self.tiff.little_endian() {
|
||||
decode_16le(src, width, height, dummy)
|
||||
} else {
|
||||
decode_16be(src, width, height, dummy)
|
||||
}
|
||||
}
|
||||
7 | 99 => self.decode_compressed(&self.camera, src, width, height, dummy)?,
|
||||
x => return Err(RawlerError::unsupported(&self.camera, format!("MOS: unsupported compression {}", x))),
|
||||
};
|
||||
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, self.get_wb()?, image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::MOS
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MosDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let meta = fetch_tiff_tag!(self.tiff, TiffCommonTag::LeafMetadata).get_data();
|
||||
let mut pos = 0;
|
||||
// We need at least 16+45+10 bytes for the NeutObj_neutrals section itself
|
||||
while pos + 70 < meta.len() {
|
||||
if meta[pos..pos + 16] == b"NeutObj_neutrals"[..] {
|
||||
let data = &meta[pos + 44..];
|
||||
if let Some(endpos) = data.iter().position(|&x| x == 0) {
|
||||
let nums = String::from_utf8_lossy(&data[0..endpos])
|
||||
.split_terminator('\n')
|
||||
.map(|x| x.parse::<f32>().unwrap_or(f32::NAN))
|
||||
.collect::<Vec<f32>>();
|
||||
if nums.len() == 4 {
|
||||
return Ok([nums[0] / nums[1], nums[0] / nums[2], nums[0] / nums[3], f32::NAN]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
|
||||
fn xmp_tag(tiff: &GenericTiffReader, tag: &str) -> Result<String> {
|
||||
let xmp_bytes = fetch_tiff_tag!(tiff, TiffCommonTag::Xmp).get_data();
|
||||
let xmp = String::from_utf8_lossy(xmp_bytes);
|
||||
let error = format!("MOS: Couldn't find XMP tag {}", tag);
|
||||
let start = xmp.find(&format!("<tiff:{}>", tag)).ok_or_else(|| error.clone())?;
|
||||
let end = xmp.find(&format!("</tiff:{}>", tag)).ok_or(error)?;
|
||||
|
||||
Ok(xmp[start + tag.len() + 7..end].to_string())
|
||||
}
|
||||
|
||||
pub fn decode_compressed(&self, cam: &Camera, src: &[u8], width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let interlaced = cam.find_hint("interlaced");
|
||||
Self::do_decode(src, interlaced, width, height, dummy)
|
||||
}
|
||||
|
||||
pub(crate) fn do_decode(src: &[u8], interlaced: bool, width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
if dummy {
|
||||
return Ok(PixU16::new_uninit(width, height));
|
||||
}
|
||||
|
||||
let decompressor = LjpegDecompressor::new_full(src, true, true)?;
|
||||
let ljpegout = decompressor.decode_leaf(width, height)?;
|
||||
if interlaced {
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
for (row, line) in ljpegout.pixels().chunks_exact(width).enumerate() {
|
||||
let orow = if row & 1 == 1 { height - 1 - row / 2 } else { row / 2 };
|
||||
out[orow * width..(orow + 1) * width].copy_from_slice(line);
|
||||
}
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(ljpegout)
|
||||
}
|
||||
}
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::BEu16;
|
||||
use crate::bits::BEu32;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::jfif::Jfif;
|
||||
use crate::formats::jfif::Segment;
|
||||
use crate::formats::jfif::is_exif;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::packed::decode_12be;
|
||||
use crate::packed::decode_12be_unpacked;
|
||||
use crate::packed::decode_16le;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::TiffCommonTag;
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::ok_cfa_image;
|
||||
|
||||
const MRW_MAGIC: u32 = 0x004D524D; // !memcmp (head,"\0MRM",4))?
|
||||
|
||||
pub fn is_mrw(file: &RawSource) -> bool {
|
||||
match file.subview(0, 4) {
|
||||
Ok(buf) => {
|
||||
if BEu32(buf, 0) == MRW_MAGIC {
|
||||
true
|
||||
} else {
|
||||
log::debug!("MRW: File MAGIC not found");
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("is_mrw() error: {:?}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MrwDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
data_offset: usize,
|
||||
raw_width: usize,
|
||||
raw_height: usize,
|
||||
bits: u8,
|
||||
packed: bool,
|
||||
wb_vals: [u16; 4],
|
||||
tiff: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> MrwDecoder<'a> {
|
||||
pub fn new(file: &RawSource, rawloader: &'a RawLoader) -> Result<MrwDecoder<'a>> {
|
||||
if is_mrw(file) {
|
||||
Self::new_mrw(file, rawloader)
|
||||
} else if is_exif(file) {
|
||||
let exif = Jfif::new(file)?;
|
||||
Self::new_jfif(file, exif, rawloader)
|
||||
} else {
|
||||
Err(crate::RawlerError::DecoderFailed(format!(
|
||||
"MRW decoder can't decode given file: {}",
|
||||
file.path().display()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Makernotes for MRW starts with "MLY" ASCII string
|
||||
fn get_mly_wb(ifd: &IFD, rawfile: &RawSource, data_offset: u64) -> Result<[u16; 4]> {
|
||||
if let Some(makernotes) = ifd.get_entry_recursive(TiffCommonTag::Makernote) {
|
||||
debug_assert_eq!(makernotes.get_data()[0..3], [b'M', b'L', b'Y']);
|
||||
if makernotes.get_data().get(0..3) == Some(b"MLY") {
|
||||
let mut buf = Cursor::new(rawfile.as_vec()?);
|
||||
let mut wb = [0_u16; 4];
|
||||
let mut cam_mul = [1_u16; 4];
|
||||
|
||||
while buf.position() < data_offset {
|
||||
wb[0] = wb[2];
|
||||
wb[2] = wb[1];
|
||||
wb[1] = wb[3];
|
||||
wb[3] = match buf.read_u16::<byteorder::BigEndian>() {
|
||||
Ok(val) => val,
|
||||
Err(_) => break,
|
||||
};
|
||||
if wb[1] == 256 && wb[3] == 256 && wb[0] > 256 && wb[0] < 640 && wb[2] > 256 && wb[2] < 640 {
|
||||
cam_mul.copy_from_slice(&wb);
|
||||
cam_mul.swap(2, 3);
|
||||
log::debug!("Found WB match: {:?}", cam_mul);
|
||||
}
|
||||
}
|
||||
return Ok(cam_mul);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Makernotes not found, fall back to defaults!");
|
||||
}
|
||||
Ok([1, 1, 1, 1])
|
||||
}
|
||||
|
||||
pub fn new_jfif(file: &RawSource, jfif: Jfif, rawloader: &'a RawLoader) -> Result<MrwDecoder<'a>> {
|
||||
let tiff = jfif
|
||||
.exif_ifd()
|
||||
.ok_or(RawlerError::DecoderFailed("No EXIF IFD found in JFIF file".to_string()))?
|
||||
.clone();
|
||||
let camera = rawloader.check_supported(&tiff)?;
|
||||
let (app1_offset, app1_len) = jfif
|
||||
.segments
|
||||
.iter()
|
||||
.map(|seg| {
|
||||
if let Segment::APP1 { offset, app1 } = seg {
|
||||
Some((*offset, app1.len))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.find(Option::is_some)
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
let data_offset = (app1_offset + app1_len + camera.param_i32("offset_corr").unwrap_or(0) as u64) as usize;
|
||||
let raw_width = camera.raw_width;
|
||||
let raw_height = camera.raw_height;
|
||||
let packed = false;
|
||||
let wb_vals = Self::get_mly_wb(&tiff, file, data_offset as u64)?;
|
||||
let bits = 16;
|
||||
|
||||
Ok(MrwDecoder {
|
||||
data_offset,
|
||||
raw_width,
|
||||
raw_height,
|
||||
bits,
|
||||
packed,
|
||||
wb_vals,
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
|
||||
fn new_mrw(file: &RawSource, rawloader: &'a RawLoader) -> Result<MrwDecoder<'a>> {
|
||||
let full = file.as_vec()?;
|
||||
let buf = &full;
|
||||
let data_offset: usize = (BEu32(buf, 4) + 8) as usize;
|
||||
let mut raw_height: usize = 0;
|
||||
let mut raw_width: usize = 0;
|
||||
let bits = 12;
|
||||
let mut packed = false;
|
||||
let mut wb_vals: [u16; 4] = [0; 4];
|
||||
let mut tiffpos: usize = 0;
|
||||
|
||||
let mut currpos: usize = 8;
|
||||
// At most we read 20 bytes from currpos so check we don't step outside that
|
||||
while currpos + 20 < data_offset {
|
||||
let tag: u32 = BEu32(buf, currpos);
|
||||
let len: u32 = BEu32(buf, currpos + 4);
|
||||
|
||||
match tag {
|
||||
0x505244 => {
|
||||
// PRD
|
||||
raw_height = BEu16(buf, currpos + 16) as usize;
|
||||
raw_width = BEu16(buf, currpos + 18) as usize;
|
||||
packed = buf[currpos + 24] == 12;
|
||||
}
|
||||
0x574247 => {
|
||||
// WBG
|
||||
for i in 0..4 {
|
||||
wb_vals[i] = BEu16(buf, currpos + 12 + i * 2);
|
||||
}
|
||||
}
|
||||
0x545457 => {
|
||||
// TTW
|
||||
// Base value for offsets needs to be at the beginning of the
|
||||
// TIFF block, not the file
|
||||
tiffpos = currpos + 8;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
currpos += (len + 8) as usize;
|
||||
}
|
||||
|
||||
let tiff_data = file.subview_until_eof(tiffpos as u64)?;
|
||||
let tiff = IFD::new(&mut Cursor::new(tiff_data), 8, 0, 0, crate::bits::Endian::Big, &[])?;
|
||||
|
||||
let camera = rawloader.check_supported(&tiff)?;
|
||||
|
||||
Ok(MrwDecoder {
|
||||
data_offset,
|
||||
raw_width,
|
||||
raw_height,
|
||||
bits,
|
||||
packed,
|
||||
wb_vals,
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for MrwDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let src = file.subview_until_eof(self.data_offset as u64)?;
|
||||
|
||||
let buffer = if self.bits == 16 {
|
||||
decode_16le(src, self.raw_width, self.raw_height, dummy)
|
||||
} else if self.packed {
|
||||
decode_12be(src, self.raw_width, self.raw_height, dummy)
|
||||
} else {
|
||||
decode_12be_unpacked(src, self.raw_width, self.raw_height, dummy)
|
||||
};
|
||||
|
||||
let wb_coeffs = if self.camera.find_hint("swapped_wb") {
|
||||
[self.wb_vals[2] as f32, self.wb_vals[0] as f32, self.wb_vals[0] as f32, self.wb_vals[1] as f32]
|
||||
} else {
|
||||
[self.wb_vals[0] as f32, self.wb_vals[1] as f32, self.wb_vals[2] as f32, self.wb_vals[3] as f32]
|
||||
};
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, normalize_wb(wb_coeffs), buffer, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(&self.tiff)?;
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::MRW
|
||||
}
|
||||
|
||||
/*
|
||||
/// File is EXIF structure, but contains no valid JPEG image, so this is useless...
|
||||
fn full_image(&self, file: &RawSource) -> Result<Option<image::DynamicImage>> {
|
||||
if is_mrw(file) {
|
||||
Ok(None)
|
||||
} else if is_exif(file) {
|
||||
let buf = file.as_vec()?;
|
||||
dump_buf("/tmp/dmp1", &buf);
|
||||
let img = image::load_from_memory_with_format(&buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to get full image from RAW file: {:?}", err)))?;
|
||||
log::debug!("Got full image from RAW");
|
||||
Ok(Some(img))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
log::debug!("MRW raw wb: {:?}", raw_wb);
|
||||
let div = raw_wb[1]; // G1 should be 1024 and we use this as divisor
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
+817
@@ -0,0 +1,817 @@
|
||||
use image::DynamicImage;
|
||||
use log::debug;
|
||||
use log::warn;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image_ok;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::BEu16;
|
||||
use crate::bits::BEu32;
|
||||
use crate::bits::Endian;
|
||||
use crate::bits::LEu32;
|
||||
use crate::bits::LookupTable;
|
||||
use crate::bits::clampbits;
|
||||
use crate::buffer::PaddedBuf;
|
||||
use crate::decoders::decode_threaded;
|
||||
use crate::decoders::dynamic_image_from_ifd;
|
||||
use crate::decoders::dynamic_image_from_jpeg_interchange_format;
|
||||
use crate::decoders::nef::lensdata::NefLensData;
|
||||
use crate::decompressors::ljpeg::huffman::HuffTable;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::packed::*;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::pumps::BitPump;
|
||||
use crate::pumps::BitPumpMSB;
|
||||
use crate::pumps::ByteStream;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawimage::WhiteLevel;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
|
||||
mod decrypt;
|
||||
pub mod lensdata;
|
||||
|
||||
const NIKON_F_MOUNT: &str = "F-mount";
|
||||
const NIKON_Z_MOUNT: &str = "Z-mount";
|
||||
|
||||
// NEF Huffman tables in order. First two are the normal huffman definitions.
|
||||
// Third one are weird shifts that are used in the lossy split encodings only
|
||||
// Values are extracted from dcraw with the shifts unmangled out.
|
||||
const NIKON_TREE: [[[u8; 16]; 3]; 6] = [
|
||||
[
|
||||
// 12-bit lossy
|
||||
[0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0],
|
||||
[5, 4, 3, 6, 2, 7, 1, 0, 8, 9, 11, 10, 12, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
[
|
||||
// 12-bit lossy after split
|
||||
[0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0],
|
||||
[6, 5, 5, 5, 5, 5, 4, 3, 2, 1, 0, 11, 12, 12, 0, 0],
|
||||
[3, 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
[
|
||||
// 12-bit lossless
|
||||
[0, 0, 1, 4, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[5, 4, 6, 3, 7, 2, 8, 1, 9, 0, 10, 11, 12, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
[
|
||||
// 14-bit lossy
|
||||
[0, 0, 1, 4, 3, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0],
|
||||
[5, 6, 4, 7, 8, 3, 9, 2, 1, 0, 10, 11, 12, 13, 14, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
[
|
||||
// 14-bit lossy after split
|
||||
[0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0],
|
||||
[8, 7, 7, 7, 7, 7, 6, 5, 4, 3, 2, 1, 0, 13, 14, 0],
|
||||
[0, 5, 4, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
[
|
||||
// 14-bit lossless
|
||||
[0, 0, 1, 4, 2, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0],
|
||||
[7, 6, 8, 5, 9, 4, 10, 3, 11, 12, 2, 0, 1, 13, 14, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
];
|
||||
|
||||
// We use this for the D50 and D2X whacky WB "encryption"
|
||||
const WB_SERIALMAP: [u8; 256] = [
|
||||
0xc1, 0xbf, 0x6d, 0x0d, 0x59, 0xc5, 0x13, 0x9d, 0x83, 0x61, 0x6b, 0x4f, 0xc7, 0x7f, 0x3d, 0x3d, 0x53, 0x59, 0xe3, 0xc7, 0xe9, 0x2f, 0x95, 0xa7, 0x95, 0x1f,
|
||||
0xdf, 0x7f, 0x2b, 0x29, 0xc7, 0x0d, 0xdf, 0x07, 0xef, 0x71, 0x89, 0x3d, 0x13, 0x3d, 0x3b, 0x13, 0xfb, 0x0d, 0x89, 0xc1, 0x65, 0x1f, 0xb3, 0x0d, 0x6b, 0x29,
|
||||
0xe3, 0xfb, 0xef, 0xa3, 0x6b, 0x47, 0x7f, 0x95, 0x35, 0xa7, 0x47, 0x4f, 0xc7, 0xf1, 0x59, 0x95, 0x35, 0x11, 0x29, 0x61, 0xf1, 0x3d, 0xb3, 0x2b, 0x0d, 0x43,
|
||||
0x89, 0xc1, 0x9d, 0x9d, 0x89, 0x65, 0xf1, 0xe9, 0xdf, 0xbf, 0x3d, 0x7f, 0x53, 0x97, 0xe5, 0xe9, 0x95, 0x17, 0x1d, 0x3d, 0x8b, 0xfb, 0xc7, 0xe3, 0x67, 0xa7,
|
||||
0x07, 0xf1, 0x71, 0xa7, 0x53, 0xb5, 0x29, 0x89, 0xe5, 0x2b, 0xa7, 0x17, 0x29, 0xe9, 0x4f, 0xc5, 0x65, 0x6d, 0x6b, 0xef, 0x0d, 0x89, 0x49, 0x2f, 0xb3, 0x43,
|
||||
0x53, 0x65, 0x1d, 0x49, 0xa3, 0x13, 0x89, 0x59, 0xef, 0x6b, 0xef, 0x65, 0x1d, 0x0b, 0x59, 0x13, 0xe3, 0x4f, 0x9d, 0xb3, 0x29, 0x43, 0x2b, 0x07, 0x1d, 0x95,
|
||||
0x59, 0x59, 0x47, 0xfb, 0xe5, 0xe9, 0x61, 0x47, 0x2f, 0x35, 0x7f, 0x17, 0x7f, 0xef, 0x7f, 0x95, 0x95, 0x71, 0xd3, 0xa3, 0x0b, 0x71, 0xa3, 0xad, 0x0b, 0x3b,
|
||||
0xb5, 0xfb, 0xa3, 0xbf, 0x4f, 0x83, 0x1d, 0xad, 0xe9, 0x2f, 0x71, 0x65, 0xa3, 0xe5, 0x07, 0x35, 0x3d, 0x0d, 0xb5, 0xe9, 0xe5, 0x47, 0x3b, 0x9d, 0xef, 0x35,
|
||||
0xa3, 0xbf, 0xb3, 0xdf, 0x53, 0xd3, 0x97, 0x53, 0x49, 0x71, 0x07, 0x35, 0x61, 0x71, 0x2f, 0x43, 0x2f, 0x11, 0xdf, 0x17, 0x97, 0xfb, 0x95, 0x3b, 0x7f, 0x6b,
|
||||
0xd3, 0x25, 0xbf, 0xad, 0xc7, 0xc5, 0xc5, 0xb5, 0x8b, 0xef, 0x2f, 0xd3, 0x07, 0x6b, 0x25, 0x49, 0x95, 0x25, 0x49, 0x6d, 0x71, 0xc7,
|
||||
];
|
||||
|
||||
const WB_KEYMAP: [u8; 256] = [
|
||||
0xa7, 0xbc, 0xc9, 0xad, 0x91, 0xdf, 0x85, 0xe5, 0xd4, 0x78, 0xd5, 0x17, 0x46, 0x7c, 0x29, 0x4c, 0x4d, 0x03, 0xe9, 0x25, 0x68, 0x11, 0x86, 0xb3, 0xbd, 0xf7,
|
||||
0x6f, 0x61, 0x22, 0xa2, 0x26, 0x34, 0x2a, 0xbe, 0x1e, 0x46, 0x14, 0x68, 0x9d, 0x44, 0x18, 0xc2, 0x40, 0xf4, 0x7e, 0x5f, 0x1b, 0xad, 0x0b, 0x94, 0xb6, 0x67,
|
||||
0xb4, 0x0b, 0xe1, 0xea, 0x95, 0x9c, 0x66, 0xdc, 0xe7, 0x5d, 0x6c, 0x05, 0xda, 0xd5, 0xdf, 0x7a, 0xef, 0xf6, 0xdb, 0x1f, 0x82, 0x4c, 0xc0, 0x68, 0x47, 0xa1,
|
||||
0xbd, 0xee, 0x39, 0x50, 0x56, 0x4a, 0xdd, 0xdf, 0xa5, 0xf8, 0xc6, 0xda, 0xca, 0x90, 0xca, 0x01, 0x42, 0x9d, 0x8b, 0x0c, 0x73, 0x43, 0x75, 0x05, 0x94, 0xde,
|
||||
0x24, 0xb3, 0x80, 0x34, 0xe5, 0x2c, 0xdc, 0x9b, 0x3f, 0xca, 0x33, 0x45, 0xd0, 0xdb, 0x5f, 0xf5, 0x52, 0xc3, 0x21, 0xda, 0xe2, 0x22, 0x72, 0x6b, 0x3e, 0xd0,
|
||||
0x5b, 0xa8, 0x87, 0x8c, 0x06, 0x5d, 0x0f, 0xdd, 0x09, 0x19, 0x93, 0xd0, 0xb9, 0xfc, 0x8b, 0x0f, 0x84, 0x60, 0x33, 0x1c, 0x9b, 0x45, 0xf1, 0xf0, 0xa3, 0x94,
|
||||
0x3a, 0x12, 0x77, 0x33, 0x4d, 0x44, 0x78, 0x28, 0x3c, 0x9e, 0xfd, 0x65, 0x57, 0x16, 0x94, 0x6b, 0xfb, 0x59, 0xd0, 0xc8, 0x22, 0x36, 0xdb, 0xd2, 0x63, 0x98,
|
||||
0x43, 0xa1, 0x04, 0x87, 0x86, 0xf7, 0xa6, 0x26, 0xbb, 0xd6, 0x59, 0x4d, 0xbf, 0x6a, 0x2e, 0xaa, 0x2b, 0xef, 0xe6, 0x78, 0xb6, 0x4e, 0xe0, 0x2f, 0xdc, 0x7c,
|
||||
0xbe, 0x57, 0x19, 0x32, 0x7e, 0x2a, 0xd0, 0xb8, 0xba, 0x29, 0x00, 0x3c, 0x52, 0x7d, 0xa8, 0x49, 0x3b, 0x2d, 0xeb, 0x25, 0x49, 0xfa, 0xa3, 0xaa, 0x39, 0xa7,
|
||||
0xc5, 0xa7, 0x50, 0x11, 0x36, 0xfb, 0xc6, 0x67, 0x4a, 0xf5, 0xa5, 0x12, 0x65, 0x7e, 0xb0, 0xdf, 0xaf, 0x4e, 0xb3, 0x61, 0x7f, 0x2f,
|
||||
];
|
||||
|
||||
/// NEF format encapsulation for analyzer
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NefFormat {
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NefDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> NefDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<NefDecoder<'a>> {
|
||||
let raw = tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::CFAPattern)
|
||||
.or_else(|| tiff.find_ifd_with_new_subfile_type(0))
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a suitable IFD in NEF decoder")))?;
|
||||
let bps = fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).force_usize(0);
|
||||
|
||||
// Make sure we always use a 12/14 bit mode to get correct white/blackpoints
|
||||
let mode = format!("{}bit", bps);
|
||||
let camera = rawloader.check_supported_with_mode(tiff.root_ifd(), &mode)?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::Absolute, &[])?
|
||||
} else {
|
||||
warn!("NEF makernote not found");
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
|
||||
//makernote.dump::<ExifTag>(0).iter().for_each(|line| eprintln!("DUMP: {}", line));
|
||||
|
||||
Ok(NefDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
makernote,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for NefDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::CFAPattern)
|
||||
.or_else(|| self.tiff.find_ifd_with_new_subfile_type(0))
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a suitable IFD in NEF decoder")))?;
|
||||
let mut width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let bps = fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).force_usize(0);
|
||||
let mut cpp = fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).count(); // Linear files don't have SamplesPerPixel
|
||||
let compression = fetch_tiff_tag!(raw, TiffCommonTag::Compression).force_usize(0);
|
||||
|
||||
let nef_compression = if let Some(z_makernote) = self.makernote.get_entry(NikonMakernote::Makernotes0x51) {
|
||||
// For new Z models, a new tag 0x51 for makernotes appears. This contains
|
||||
// The new-old NEFCompression tag. The old tag is unavailable in this models.
|
||||
Some(NefCompression::try_from(crate::bits::LEu16(z_makernote.get_data(), 10)).map_err(RawlerError::from)?)
|
||||
} else {
|
||||
self
|
||||
.makernote
|
||||
.get_entry(NikonMakernote::NefCompression)
|
||||
.map(|entry| entry.force_u16(0))
|
||||
.map(NefCompression::try_from)
|
||||
.transpose()
|
||||
.map_err(RawlerError::from)?
|
||||
};
|
||||
debug!("TIFF compression flag: {}, NEF compression mode: {:?}", compression, nef_compression);
|
||||
|
||||
if matches!(nef_compression, Some(NefCompression::HighEfficency)) || matches!(nef_compression, Some(NefCompression::HighEfficencyStar)) {
|
||||
return Err(RawlerError::DecoderFailed(format!("NEF compression {:?} is not supported", nef_compression)));
|
||||
}
|
||||
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let size = fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts).force_usize(0);
|
||||
let rows_per_strip = fetch_tiff_tag!(raw, TiffCommonTag::RowsPerStrip).get_usize(0).ok().flatten().unwrap_or(height);
|
||||
|
||||
// That's little bit hacky here. Some files like D500 using multiple strips.
|
||||
// Because the strips has no holes between and are perfectly aligned, we can process the whole
|
||||
// chunk at once, instead of iterating over every strip.
|
||||
// It would be safer to process each strip offset, but it is not need for any known model so far.
|
||||
let src = if rows_per_strip == height {
|
||||
file.subview_padded(offset as u64, size as u64)?
|
||||
} else {
|
||||
let full_size: u32 = match fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts) {
|
||||
Value::Long(data) => data.iter().copied().sum(),
|
||||
_ => {
|
||||
return Err("StripByteCounts is not of type LONG".into());
|
||||
}
|
||||
};
|
||||
file.subview_padded(offset as u64, full_size as u64)?
|
||||
};
|
||||
|
||||
let coeffs = normalize_wb(self.get_wb()?);
|
||||
debug!("WB coeff: {:?}", coeffs);
|
||||
|
||||
assert_eq!(self.tiff.little_endian(), self.makernote.endian == Endian::Little);
|
||||
|
||||
let image = if self.camera.model == "NIKON D100" {
|
||||
width = 3040;
|
||||
decode_12be_wcontrol(&src, width, height, dummy)
|
||||
} else if self.camera.find_hint("coolpixsplit") {
|
||||
decode_12be_interlaced_unaligned(&src, width, height, dummy)
|
||||
} else if self.camera.find_hint("msb32") {
|
||||
decode_12be_msb32(&src, width, height, dummy)
|
||||
} else if self.camera.find_hint("unpacked") {
|
||||
// P7800 and others is LE, but data is BE, so we use hints here
|
||||
if (self.tiff.little_endian() || self.camera.find_hint("little_endian")) && !self.camera.find_hint("big_endian") {
|
||||
decode_16le(&src, width, height, dummy)
|
||||
} else {
|
||||
decode_16be(&src, width, height, dummy)
|
||||
}
|
||||
} else if let Some(padding) = self.is_uncompressed(raw)? {
|
||||
debug!("NEF uncompressed row padding: {}, little-endian: {}", padding, self.tiff.little_endian());
|
||||
match bps {
|
||||
16 => {
|
||||
// Used by Coolscan scanners
|
||||
if self.tiff.little_endian() {
|
||||
decode_16le(&src, width * cpp, height, dummy)
|
||||
} else {
|
||||
decode_16be(&src, width * cpp, height, dummy)
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
if (self.tiff.little_endian() || self.camera.find_hint("little_endian")) && !self.camera.find_hint("big_endian") {
|
||||
// Models like D6 uses packed instead of unpacked 14le encoding. And D6 uses
|
||||
// row padding.
|
||||
if matches!(nef_compression, Some(NefCompression::Packed14Bits)) {
|
||||
decode_14le_padded(&src, width, height, (width * bps / u8::BITS as usize) + padding, dummy)
|
||||
} else {
|
||||
decode_14le_unpacked(&src, width, height, dummy)
|
||||
}
|
||||
} else {
|
||||
decode_14be_unpacked(&src, width, height, dummy)
|
||||
}
|
||||
}
|
||||
12 => {
|
||||
if (self.tiff.little_endian() || self.camera.find_hint("little_endian")) && !self.camera.find_hint("big_endian") {
|
||||
decode_12le_padded(&src, width, height, (width * bps / u8::BITS as usize) + padding, dummy)
|
||||
} else {
|
||||
decode_12be(&src, width, height, dummy)
|
||||
}
|
||||
}
|
||||
x => return Err(RawlerError::unsupported(&self.camera, format!("Don't know uncompressed bps {}", x))),
|
||||
}
|
||||
} else if size == width * height * 3 {
|
||||
cpp = 3;
|
||||
Self::decode_snef_compressed(&src, coeffs, width, height, dummy)
|
||||
} else if compression == 34713 {
|
||||
self.decode_compressed(&src, width, height, bps, dummy)?
|
||||
} else {
|
||||
return Err(RawlerError::unsupported(&self.camera, format!("NEF: Don't know compression {}", compression)));
|
||||
};
|
||||
|
||||
assert_eq!(image.width, width * cpp);
|
||||
let blacklevel = self.get_blacklevel(bps)?;
|
||||
let whitelevel = None;
|
||||
let photometric = match cpp {
|
||||
1 => RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera)),
|
||||
3 => RawPhotometricInterpretation::LinearRaw,
|
||||
_ => todo!(),
|
||||
};
|
||||
let mut img = RawImage::new(self.camera.clone(), image, cpp, coeffs, photometric, blacklevel, whitelevel, dummy);
|
||||
|
||||
if let Some(crop) = self.get_crop()? {
|
||||
debug!("RAW Crops: {:?}", crop);
|
||||
img.crop_area = Some(crop);
|
||||
}
|
||||
|
||||
if cpp == 3 {
|
||||
// Reset levels to defaults (0)
|
||||
img.blacklevel = BlackLevel::default();
|
||||
img.whitelevel = WhiteLevel::new(vec![65535; cpp]);
|
||||
}
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
FormatDump::Nef(NefFormat { tiff: self.tiff.clone() })
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
Ok(match self.get_lens_description() {
|
||||
Ok(lens_data) => RawMetadata::new_with_lens(&self.camera, exif, lens_data.cloned()),
|
||||
Err(err) => {
|
||||
log::warn!("Failed to read lens information: {:?}", err);
|
||||
RawMetadata::new(&self.camera, exif)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
// High resolution preview image is stored in JPEGInterchangeFormat tag.
|
||||
// Search for all IFDs and use the best match.
|
||||
let mut ifds = self.tiff.find_ifds_with_filter(|ifd| {
|
||||
if ifd.get_new_sub_file_type() == Some(1) {
|
||||
ifd.get_entry(ExifTag::JPEGInterchangeFormatLength).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
ifds.sort_by(|a, b| {
|
||||
a.get_entry(ExifTag::JPEGInterchangeFormatLength)
|
||||
.map(|x| x.force_u32(0))
|
||||
.cmp(&b.get_entry(ExifTag::JPEGInterchangeFormatLength).map(|x| x.force_u32(0)))
|
||||
});
|
||||
|
||||
// Take the IFD with the largest JPEG stream size
|
||||
if let Some(jpeg_ifd) = ifds.last() {
|
||||
return Ok(Some(dynamic_image_from_jpeg_interchange_format(jpeg_ifd, file)?));
|
||||
} else {
|
||||
// No matching IFDs found, use root IFD (possibly bad resolution)
|
||||
Ok(Some(dynamic_image_from_ifd(self.tiff.root_ifd(), file)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::NEF
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NefDecoder<'a> {
|
||||
/// For older formats, we use the camera definitions and this here
|
||||
/// is useless. But if we found here the levels in makernotes, we
|
||||
/// use these instead. For 12 bit images, the blacklevels are still relative to
|
||||
/// 14 bit image data. So we need to reduce them by 2 bits.
|
||||
fn get_blacklevel(&self, bps: usize) -> Result<Option<BlackLevel>> {
|
||||
if let Some(levels) = self.makernote.get_entry(NikonMakernote::BlackLevel) {
|
||||
let mut black = [levels.force_u16(0), levels.force_u16(1), levels.force_u16(2), levels.force_u16(3)];
|
||||
if bps == 12 {
|
||||
black.iter_mut().for_each(|v| *v >>= 14 - 12);
|
||||
}
|
||||
Ok(Some(BlackLevel::new(&black, self.camera.cfa.width, self.camera.cfa.height, 1)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_crop(&self) -> Result<Option<Rect>> {
|
||||
if let Some(crop) = self.makernote.get_entry(NikonMakernote::CropArea) {
|
||||
let values = [crop.force_u16(0), crop.force_u16(1), crop.force_u16(2), crop.force_u16(3)];
|
||||
let rect = Rect::new(
|
||||
Point::new(values[0] as usize, values[1] as usize),
|
||||
Dim2::new(values[2] as usize, values[3] as usize),
|
||||
);
|
||||
Ok(Some(rect))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
if let Some(lensdata) = lensdata::from_makernote(&self.makernote)? {
|
||||
if let Some(lenstype) = self.makernote.get_entry(NikonMakernote::LensType) {
|
||||
match lensdata {
|
||||
NefLensData::FMount(oldv) => {
|
||||
let composite_id = oldv.composite_id(lenstype.force_u8(0));
|
||||
log::debug!("NEF lens composite ID: {}", composite_id);
|
||||
let resolver = LensResolver::new()
|
||||
.with_nikon_id(Some(composite_id))
|
||||
.with_camera(&self.camera)
|
||||
.with_mounts(&[NIKON_F_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
NefLensData::ZMount(newv) => {
|
||||
let resolver = LensResolver::new()
|
||||
.with_lens_id((newv.lens_id as u32, 0))
|
||||
.with_camera(&self.camera)
|
||||
.with_mounts(&[NIKON_Z_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
if self.camera.find_hint("nowb") {
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
} else if let Some(levels) = self.makernote.get_entry(TiffCommonTag::NefWB0) {
|
||||
Ok([levels.force_f32(0), 1.0, 1.0, levels.force_f32(1)])
|
||||
} else if let Some(levels) = self.makernote.get_entry(TiffCommonTag::NrwWB) {
|
||||
let data = levels.get_data();
|
||||
if data[0..3] == b"NRW"[..] {
|
||||
let offset = if data[4..8] == b"0100"[..] { 1556 } else { 56 };
|
||||
Ok([
|
||||
(LEu32(data, offset) << 2) as f32,
|
||||
(LEu32(data, offset + 4) + LEu32(data, offset + 8)) as f32,
|
||||
(LEu32(data, offset + 4) + LEu32(data, offset + 8)) as f32,
|
||||
(LEu32(data, offset + 12) << 2) as f32,
|
||||
])
|
||||
} else {
|
||||
Ok([BEu16(data, 1248) as f32, 256.0, 256.0, BEu16(data, 1250) as f32])
|
||||
}
|
||||
} else if let Some(levels) = self.makernote.get_entry(TiffCommonTag::NefWB1) {
|
||||
let mut version: u32 = 0;
|
||||
for i in 0..4 {
|
||||
version = (version << 4) + (levels.get_data()[i] - b'0') as u32;
|
||||
}
|
||||
let buf = levels.get_data();
|
||||
debug!("NEF Color balance version: 0x{:x}", version);
|
||||
|
||||
match version {
|
||||
0x100 => Ok([
|
||||
BEu16(buf, 36 * 2) as f32,
|
||||
BEu16(buf, 38 * 2) as f32,
|
||||
BEu16(buf, 38 * 2) as f32,
|
||||
BEu16(buf, 37 * 2) as f32,
|
||||
]),
|
||||
// Nikon D2H
|
||||
0x102 => Ok([
|
||||
BEu16(buf, 5 * 2) as f32,
|
||||
BEu16(buf, 6 * 2) as f32,
|
||||
BEu16(buf, 6 * 2) as f32,
|
||||
BEu16(buf, 8 * 2) as f32,
|
||||
]),
|
||||
// Nikon D70
|
||||
0x103 => Ok([
|
||||
BEu16(buf, 10 * 2) as f32,
|
||||
BEu16(buf, 11 * 2) as f32,
|
||||
BEu16(buf, 11 * 2) as f32,
|
||||
BEu16(buf, 12 * 2) as f32,
|
||||
]),
|
||||
0x204 | 0x205 => {
|
||||
let serial = fetch_tiff_tag!(self.makernote, TiffCommonTag::NefSerial);
|
||||
let data = serial.get_data();
|
||||
let mut serialno = 0_usize;
|
||||
for i in 0..serial.count() as usize {
|
||||
if data[i] == 0 {
|
||||
break;
|
||||
}
|
||||
serialno = serialno * 10
|
||||
+ if data[i] >= 48 && data[i] <= 57 {
|
||||
// "0" to "9"
|
||||
(data[i] - 48) as usize
|
||||
} else {
|
||||
(data[i] % 10) as usize
|
||||
};
|
||||
}
|
||||
|
||||
// Get the "decryption" key
|
||||
let keydata = fetch_tiff_tag!(self.makernote, TiffCommonTag::NefKey).force_u32(0).to_le_bytes();
|
||||
let keyno = (keydata[0] ^ keydata[1] ^ keydata[2] ^ keydata[3]) as usize;
|
||||
|
||||
let src = if version == 0x204 {
|
||||
&levels.get_data()[284..]
|
||||
} else {
|
||||
&levels.get_data()[4..]
|
||||
};
|
||||
|
||||
let ci = WB_SERIALMAP[serialno & 0xff] as u32;
|
||||
let mut cj = WB_KEYMAP[keyno & 0xff] as u32;
|
||||
let mut ck = 0x60_u32;
|
||||
let mut buf = [0_u8; 280];
|
||||
for i in 0..280 {
|
||||
cj += ci * ck;
|
||||
ck += 1;
|
||||
buf[i] = src[i] ^ (cj as u8);
|
||||
}
|
||||
|
||||
let off = if version == 0x204 { 6 } else { 14 };
|
||||
Ok([
|
||||
BEu16(&buf, off) as f32,
|
||||
BEu16(&buf, off + 2) as f32,
|
||||
BEu16(&buf, off + 4) as f32,
|
||||
BEu16(&buf, off + 6) as f32,
|
||||
])
|
||||
}
|
||||
x => Err(RawlerError::unsupported(&self.camera, format!("NEF: Don't know about WB version 0x{:x}", x))),
|
||||
}
|
||||
} else {
|
||||
log::debug!("NEF: Don't know how to fetch WB, fallback to [1.0, 1.0, 1.0]");
|
||||
Ok([1.0, 1.0, 1.0, 1.0])
|
||||
}
|
||||
}
|
||||
|
||||
fn create_hufftable(num: usize) -> Result<HuffTable> {
|
||||
let mut htable = HuffTable::empty();
|
||||
|
||||
for i in 0..15 {
|
||||
htable.bits[i] = NIKON_TREE[num][0][i] as u32;
|
||||
htable.huffval[i] = NIKON_TREE[num][1][i] as u32;
|
||||
htable.shiftval[i] = NIKON_TREE[num][2][i] as u32;
|
||||
}
|
||||
|
||||
htable.initialize()?;
|
||||
Ok(htable)
|
||||
}
|
||||
|
||||
/// The compression flags in some raws are not reliable because of firmware bugs.
|
||||
/// We try to figure out the compression by some heuristics.
|
||||
/// The return value is None if the file is not uncompressed or Some(x)
|
||||
/// where x is the extra amount of bytes after each row.
|
||||
fn is_uncompressed(&self, raw: &IFD) -> Result<Option<usize>> {
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let bps = fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).force_usize(0);
|
||||
let compression = fetch_tiff_tag!(raw, TiffCommonTag::Compression).force_usize(0);
|
||||
let size = fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts).force_usize(0);
|
||||
|
||||
fn div_round_up(a: usize, b: usize) -> usize {
|
||||
a.div_ceil(b) // (a + b - 1) / b
|
||||
}
|
||||
|
||||
let req_pixels = width * height;
|
||||
let req_input_bits = bps * req_pixels;
|
||||
let req_input_bytes = div_round_up(req_input_bits, 8);
|
||||
|
||||
Ok(if compression == 1 || size == width * height * bps / 8 {
|
||||
Some(0)
|
||||
} else if size >= req_input_bytes {
|
||||
// Some models (D6) using row padding, so the row width is slightly larger.
|
||||
// This should be no more than 16 extra bytes.
|
||||
let total_padding = size - req_input_bytes;
|
||||
let per_row_padding = total_padding / height;
|
||||
if total_padding % height != 0 {
|
||||
None
|
||||
} else if per_row_padding < 16 {
|
||||
Some(per_row_padding)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_compressed(&self, src: &PaddedBuf, width: usize, height: usize, bps: usize, dummy: bool) -> Result<PixU16> {
|
||||
let meta = if let Some(meta) = self.makernote.get_entry(TiffCommonTag::NefMeta2) {
|
||||
debug!("Found NefMeta2");
|
||||
meta
|
||||
} else {
|
||||
debug!("Fallback NefMeta1");
|
||||
fetch_tiff_tag!(self.makernote, TiffCommonTag::NefMeta1)
|
||||
};
|
||||
Self::do_decode(src, meta.get_data(), self.makernote.endian, width, height, bps, dummy)
|
||||
}
|
||||
|
||||
pub(crate) fn do_decode(src: &[u8], meta: &[u8], endian: Endian, width: usize, height: usize, bps: usize, dummy: bool) -> Result<PixU16> {
|
||||
debug!("NEF decode with: endian: {:?}, width: {}, height: {}, bps: {}", endian, width, height, bps);
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
let mut stream = ByteStream::new(meta, endian);
|
||||
let v0 = stream.get_u8();
|
||||
let v1 = stream.get_u8();
|
||||
debug!("Nef version v0:{}, v1:{}", v0, v1);
|
||||
|
||||
let mut huff_select = 0;
|
||||
if v0 == 73 || v1 == 88 {
|
||||
assert!(stream.remaining_bytes() >= 2110);
|
||||
stream.consume_bytes(2110);
|
||||
}
|
||||
if v0 == 70 {
|
||||
huff_select = 2;
|
||||
}
|
||||
if bps == 14 {
|
||||
huff_select += 3;
|
||||
}
|
||||
|
||||
// Create the huffman table used to decode
|
||||
let mut htable = Self::create_hufftable(huff_select)?;
|
||||
|
||||
// Setup the predictors
|
||||
let mut pred_up1: [i32; 2] = [stream.get_u16() as i32, stream.get_u16() as i32];
|
||||
let mut pred_up2: [i32; 2] = [stream.get_u16() as i32, stream.get_u16() as i32];
|
||||
|
||||
// Get the linearization curve
|
||||
let mut points = [0_u16; 1 << 16];
|
||||
for i in 0..points.len() {
|
||||
points[i] = i as u16;
|
||||
}
|
||||
|
||||
// Some models reports 14 bits, but the data is 12 bits.
|
||||
// So we reduce the bps to calculate the max value which
|
||||
// is needed in the next steps.
|
||||
let real_bps = if v0 == 68 && v1 == 64 {
|
||||
bps as u32 - 2 // Special for D780, Z7 and others
|
||||
} else {
|
||||
bps as u32
|
||||
};
|
||||
let mut max = 1 << real_bps;
|
||||
|
||||
let csize = stream.get_u16() as usize;
|
||||
let mut split = 0_usize;
|
||||
let step = if csize > 1 { max / (csize - 1) } else { 0 };
|
||||
if v0 == 68 && (v1 == 32 || v1 == 64) && step > 0 {
|
||||
for i in 0..csize {
|
||||
points[i * step] = stream.get_u16();
|
||||
}
|
||||
for i in 0..max {
|
||||
let b_scale = i % step;
|
||||
let a_pos = i - b_scale;
|
||||
let b_pos = a_pos + step;
|
||||
//assert!(a_pos < max);
|
||||
//assert!(b_pos > 0);
|
||||
//assert!(b_pos < max);
|
||||
//assert!(a_pos < b_pos);
|
||||
let a_scale = step - b_scale;
|
||||
points[i] = ((a_scale * points[a_pos] as usize + b_scale * points[b_pos] as usize) / step) as u16;
|
||||
}
|
||||
split = endian.read_u16(meta, 562) as usize;
|
||||
} else if v0 != 70 && csize <= 0x4001 {
|
||||
for i in 0..csize {
|
||||
points[i] = stream.get_u16();
|
||||
}
|
||||
max = csize;
|
||||
}
|
||||
let curve = LookupTable::new(&points[0..max]);
|
||||
|
||||
let mut pump = BitPumpMSB::new(src);
|
||||
let mut random = pump.peek_bits(24);
|
||||
|
||||
for row in 0..height {
|
||||
if split > 0 && row == split {
|
||||
htable = Self::create_hufftable(huff_select + 1)?;
|
||||
}
|
||||
pred_up1[row & 1] += htable.huff_decode(&mut pump)?;
|
||||
pred_up2[row & 1] += htable.huff_decode(&mut pump)?;
|
||||
let mut pred_left1 = pred_up1[row & 1];
|
||||
let mut pred_left2 = pred_up2[row & 1];
|
||||
for col in (0..width).step_by(2) {
|
||||
if col > 0 {
|
||||
pred_left1 += htable.huff_decode(&mut pump)?;
|
||||
pred_left2 += htable.huff_decode(&mut pump)?;
|
||||
}
|
||||
out[row * width + col + 0] = curve.dither(clampbits(pred_left1, real_bps), &mut random);
|
||||
out[row * width + col + 1] = curve.dither(clampbits(pred_left2, real_bps), &mut random);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
// Decodes 12 bit data in an YUY2-like pattern (2 Luma, 1 Chroma per 2 pixels).
|
||||
// We un-apply the whitebalance, so output matches lossless.
|
||||
pub(crate) fn decode_snef_compressed(src: &PaddedBuf, coeffs: [f32; 4], width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
let inv_wb_r = (1024.0 / coeffs[0]) as i32;
|
||||
let inv_wb_b = (1024.0 / coeffs[2]) as i32;
|
||||
|
||||
//println!("Got invwb {} {}", inv_wb_r, inv_wb_b);
|
||||
|
||||
let snef_curve = {
|
||||
let g: f32 = 2.4;
|
||||
let f: f32 = 0.055;
|
||||
let min: f32 = 0.04045;
|
||||
let mul: f32 = 12.92;
|
||||
let curve = (0..4096)
|
||||
.map(|i| {
|
||||
let v = (i as f32) / 4095.0;
|
||||
let res = if v <= min { v / mul } else { ((v + f) / (1.0 + f)).powf(g) };
|
||||
clampbits((res * 65535.0 * 4.0) as i32, 16)
|
||||
})
|
||||
.collect::<Vec<u16>>();
|
||||
LookupTable::new(&curve)
|
||||
};
|
||||
|
||||
decode_threaded(
|
||||
width * 3,
|
||||
height,
|
||||
dummy,
|
||||
&(|out: &mut [u16], row| {
|
||||
let inb = &src[row * width * 3..];
|
||||
let mut random = BEu32(inb, 0);
|
||||
for (o, i) in out.chunks_exact_mut(6).zip(inb.chunks_exact(6)) {
|
||||
let g1: u16 = i[0] as u16;
|
||||
let g2: u16 = i[1] as u16;
|
||||
let g3: u16 = i[2] as u16;
|
||||
let g4: u16 = i[3] as u16;
|
||||
let g5: u16 = i[4] as u16;
|
||||
let g6: u16 = i[5] as u16;
|
||||
|
||||
let y1 = (g1 | ((g2 & 0x0f) << 8)) as f32;
|
||||
let y2 = ((g2 >> 4) | (g3 << 4)) as f32;
|
||||
let cb = (g4 | ((g5 & 0x0f) << 8)) as f32 - 2048.0;
|
||||
let cr = ((g5 >> 4) | (g6 << 4)) as f32 - 2048.0;
|
||||
|
||||
let r = snef_curve.dither(clampbits((y1 + 1.370705 * cr) as i32, 12), &mut random);
|
||||
let g = snef_curve.dither(clampbits((y1 - 0.337633 * cb - 0.698001 * cr) as i32, 12), &mut random);
|
||||
let b = snef_curve.dither(clampbits((y1 + 1.732446 * cb) as i32, 12), &mut random);
|
||||
// invert the white balance
|
||||
o[0] = clampbits((inv_wb_r * r as i32 + (1 << 9)) >> 10, 15);
|
||||
o[1] = g;
|
||||
o[2] = clampbits((inv_wb_b * b as i32 + (1 << 9)) >> 10, 15);
|
||||
|
||||
let r = snef_curve.dither(clampbits((y2 + 1.370705 * cr) as i32, 12), &mut random);
|
||||
let g = snef_curve.dither(clampbits((y2 - 0.337633 * cb - 0.698001 * cr) as i32, 12), &mut random);
|
||||
let b = snef_curve.dither(clampbits((y2 + 1.732446 * cb) as i32, 12), &mut random);
|
||||
// invert the white balance
|
||||
o[3] = clampbits((inv_wb_r * r as i32 + (1 << 9)) >> 10, 15);
|
||||
o[4] = g;
|
||||
o[5] = clampbits((inv_wb_b * b as i32 + (1 << 9)) >> 10, 15);
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
debug!("NEF raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1];
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(NikonMakernote);
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum NikonMakernote {
|
||||
MakernoteVersion = 0x0001,
|
||||
NefWB0 = 0x000C,
|
||||
PreviewIFD = 0x0011,
|
||||
NrwWB = 0x0014,
|
||||
NefSerial = 0x001d,
|
||||
ImageSizeRaw = 0x003e,
|
||||
CropArea = 0x0045,
|
||||
BlackLevel = 0x003d,
|
||||
Makernotes0x51 = 0x0051,
|
||||
LensType = 0x0083,
|
||||
NefMeta1 = 0x008c,
|
||||
NefMeta2 = 0x0096,
|
||||
ShotInfo = 0x0091,
|
||||
NefCompression = 0x0093,
|
||||
NefWB1 = 0x0097,
|
||||
LensData = 0x0098,
|
||||
NefKey = 0x00a7,
|
||||
}
|
||||
|
||||
/// Known NEF compression formats
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
enum NefCompression {
|
||||
LossyType1 = 1,
|
||||
Uncompressed = 2,
|
||||
Lossless = 3,
|
||||
LossyType2 = 4,
|
||||
StripedPacked12Bits = 5,
|
||||
UncompressedReduced12Bits = 6,
|
||||
Unpacked12Bits = 7,
|
||||
Small = 8,
|
||||
Packed12Bits = 9,
|
||||
Packed14Bits = 10,
|
||||
HighEfficency = 13,
|
||||
HighEfficencyStar = 14,
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for NefCompression {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(v: u16) -> std::result::Result<Self, Self::Error> {
|
||||
Ok(match v {
|
||||
1 => Self::LossyType1,
|
||||
2 => Self::Uncompressed,
|
||||
3 => Self::Lossless,
|
||||
4 => Self::LossyType2,
|
||||
5 => Self::StripedPacked12Bits,
|
||||
6 => Self::UncompressedReduced12Bits,
|
||||
7 => Self::Unpacked12Bits,
|
||||
8 => Self::Small,
|
||||
9 => Self::Packed12Bits,
|
||||
10 => Self::Packed14Bits,
|
||||
13 => Self::HighEfficency,
|
||||
14 => Self::HighEfficencyStar,
|
||||
_ => return Err(format!("unknown nef compression: {}", v)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::formats::tiff::IFD;
|
||||
|
||||
use super::NikonMakernote;
|
||||
use crate::Result;
|
||||
|
||||
pub(super) fn nef_decrypt(buf: &mut [u8], start: usize, makernote: &IFD) -> Result<()> {
|
||||
let serial = fetch_tiff_tag!(makernote, NikonMakernote::NefSerial);
|
||||
let data = serial.get_data();
|
||||
let mut serialno = 0_usize;
|
||||
for i in 0..serial.count() as usize {
|
||||
if data[i] == 0 {
|
||||
break;
|
||||
}
|
||||
serialno = serialno * 10
|
||||
+ if data[i] >= 48 && data[i] <= 57 {
|
||||
// "0" to "9"
|
||||
(data[i] - 48) as usize
|
||||
} else {
|
||||
(data[i] % 10) as usize
|
||||
};
|
||||
}
|
||||
|
||||
// Get the "decryption" key
|
||||
let keydata = fetch_tiff_tag!(makernote, NikonMakernote::NefKey).force_u32(0).to_le_bytes();
|
||||
let keyno = (keydata[0] ^ keydata[1] ^ keydata[2] ^ keydata[3]) as usize;
|
||||
|
||||
let src = &mut buf[start..];
|
||||
|
||||
let ci = WB_SERIALMAP[serialno & 0xff] as u32;
|
||||
let mut cj = WB_KEYMAP[keyno & 0xff] as u32;
|
||||
let mut ck = 0x60_u32;
|
||||
for i in 0..src.len() {
|
||||
cj += ci * ck;
|
||||
ck += 1;
|
||||
src[i] ^= cj as u8;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// We use this for the D50 and D2X whacky WB "encryption"
|
||||
const WB_SERIALMAP: [u8; 256] = [
|
||||
0xc1, 0xbf, 0x6d, 0x0d, 0x59, 0xc5, 0x13, 0x9d, 0x83, 0x61, 0x6b, 0x4f, 0xc7, 0x7f, 0x3d, 0x3d, 0x53, 0x59, 0xe3, 0xc7, 0xe9, 0x2f, 0x95, 0xa7, 0x95, 0x1f,
|
||||
0xdf, 0x7f, 0x2b, 0x29, 0xc7, 0x0d, 0xdf, 0x07, 0xef, 0x71, 0x89, 0x3d, 0x13, 0x3d, 0x3b, 0x13, 0xfb, 0x0d, 0x89, 0xc1, 0x65, 0x1f, 0xb3, 0x0d, 0x6b, 0x29,
|
||||
0xe3, 0xfb, 0xef, 0xa3, 0x6b, 0x47, 0x7f, 0x95, 0x35, 0xa7, 0x47, 0x4f, 0xc7, 0xf1, 0x59, 0x95, 0x35, 0x11, 0x29, 0x61, 0xf1, 0x3d, 0xb3, 0x2b, 0x0d, 0x43,
|
||||
0x89, 0xc1, 0x9d, 0x9d, 0x89, 0x65, 0xf1, 0xe9, 0xdf, 0xbf, 0x3d, 0x7f, 0x53, 0x97, 0xe5, 0xe9, 0x95, 0x17, 0x1d, 0x3d, 0x8b, 0xfb, 0xc7, 0xe3, 0x67, 0xa7,
|
||||
0x07, 0xf1, 0x71, 0xa7, 0x53, 0xb5, 0x29, 0x89, 0xe5, 0x2b, 0xa7, 0x17, 0x29, 0xe9, 0x4f, 0xc5, 0x65, 0x6d, 0x6b, 0xef, 0x0d, 0x89, 0x49, 0x2f, 0xb3, 0x43,
|
||||
0x53, 0x65, 0x1d, 0x49, 0xa3, 0x13, 0x89, 0x59, 0xef, 0x6b, 0xef, 0x65, 0x1d, 0x0b, 0x59, 0x13, 0xe3, 0x4f, 0x9d, 0xb3, 0x29, 0x43, 0x2b, 0x07, 0x1d, 0x95,
|
||||
0x59, 0x59, 0x47, 0xfb, 0xe5, 0xe9, 0x61, 0x47, 0x2f, 0x35, 0x7f, 0x17, 0x7f, 0xef, 0x7f, 0x95, 0x95, 0x71, 0xd3, 0xa3, 0x0b, 0x71, 0xa3, 0xad, 0x0b, 0x3b,
|
||||
0xb5, 0xfb, 0xa3, 0xbf, 0x4f, 0x83, 0x1d, 0xad, 0xe9, 0x2f, 0x71, 0x65, 0xa3, 0xe5, 0x07, 0x35, 0x3d, 0x0d, 0xb5, 0xe9, 0xe5, 0x47, 0x3b, 0x9d, 0xef, 0x35,
|
||||
0xa3, 0xbf, 0xb3, 0xdf, 0x53, 0xd3, 0x97, 0x53, 0x49, 0x71, 0x07, 0x35, 0x61, 0x71, 0x2f, 0x43, 0x2f, 0x11, 0xdf, 0x17, 0x97, 0xfb, 0x95, 0x3b, 0x7f, 0x6b,
|
||||
0xd3, 0x25, 0xbf, 0xad, 0xc7, 0xc5, 0xc5, 0xb5, 0x8b, 0xef, 0x2f, 0xd3, 0x07, 0x6b, 0x25, 0x49, 0x95, 0x25, 0x49, 0x6d, 0x71, 0xc7,
|
||||
];
|
||||
|
||||
const WB_KEYMAP: [u8; 256] = [
|
||||
0xa7, 0xbc, 0xc9, 0xad, 0x91, 0xdf, 0x85, 0xe5, 0xd4, 0x78, 0xd5, 0x17, 0x46, 0x7c, 0x29, 0x4c, 0x4d, 0x03, 0xe9, 0x25, 0x68, 0x11, 0x86, 0xb3, 0xbd, 0xf7,
|
||||
0x6f, 0x61, 0x22, 0xa2, 0x26, 0x34, 0x2a, 0xbe, 0x1e, 0x46, 0x14, 0x68, 0x9d, 0x44, 0x18, 0xc2, 0x40, 0xf4, 0x7e, 0x5f, 0x1b, 0xad, 0x0b, 0x94, 0xb6, 0x67,
|
||||
0xb4, 0x0b, 0xe1, 0xea, 0x95, 0x9c, 0x66, 0xdc, 0xe7, 0x5d, 0x6c, 0x05, 0xda, 0xd5, 0xdf, 0x7a, 0xef, 0xf6, 0xdb, 0x1f, 0x82, 0x4c, 0xc0, 0x68, 0x47, 0xa1,
|
||||
0xbd, 0xee, 0x39, 0x50, 0x56, 0x4a, 0xdd, 0xdf, 0xa5, 0xf8, 0xc6, 0xda, 0xca, 0x90, 0xca, 0x01, 0x42, 0x9d, 0x8b, 0x0c, 0x73, 0x43, 0x75, 0x05, 0x94, 0xde,
|
||||
0x24, 0xb3, 0x80, 0x34, 0xe5, 0x2c, 0xdc, 0x9b, 0x3f, 0xca, 0x33, 0x45, 0xd0, 0xdb, 0x5f, 0xf5, 0x52, 0xc3, 0x21, 0xda, 0xe2, 0x22, 0x72, 0x6b, 0x3e, 0xd0,
|
||||
0x5b, 0xa8, 0x87, 0x8c, 0x06, 0x5d, 0x0f, 0xdd, 0x09, 0x19, 0x93, 0xd0, 0xb9, 0xfc, 0x8b, 0x0f, 0x84, 0x60, 0x33, 0x1c, 0x9b, 0x45, 0xf1, 0xf0, 0xa3, 0x94,
|
||||
0x3a, 0x12, 0x77, 0x33, 0x4d, 0x44, 0x78, 0x28, 0x3c, 0x9e, 0xfd, 0x65, 0x57, 0x16, 0x94, 0x6b, 0xfb, 0x59, 0xd0, 0xc8, 0x22, 0x36, 0xdb, 0xd2, 0x63, 0x98,
|
||||
0x43, 0xa1, 0x04, 0x87, 0x86, 0xf7, 0xa6, 0x26, 0xbb, 0xd6, 0x59, 0x4d, 0xbf, 0x6a, 0x2e, 0xaa, 0x2b, 0xef, 0xe6, 0x78, 0xb6, 0x4e, 0xe0, 0x2f, 0xdc, 0x7c,
|
||||
0xbe, 0x57, 0x19, 0x32, 0x7e, 0x2a, 0xd0, 0xb8, 0xba, 0x29, 0x00, 0x3c, 0x52, 0x7d, 0xa8, 0x49, 0x3b, 0x2d, 0xeb, 0x25, 0x49, 0xfa, 0xa3, 0xaa, 0x39, 0xa7,
|
||||
0xc5, 0xa7, 0x50, 0x11, 0x36, 0xfb, 0xc6, 0x67, 0x4a, 0xf5, 0xa5, 0x12, 0x65, 0x7e, 0xb0, 0xdf, 0xaf, 0x4e, 0xb3, 0x61, 0x7f, 0x2f,
|
||||
];
|
||||
@@ -0,0 +1,283 @@
|
||||
use crate::Result;
|
||||
use crate::bits::LEu16;
|
||||
use crate::{decoders::nef::NikonMakernote, formats::tiff::IFD};
|
||||
|
||||
const ERRMSG: &str = "Lens composite buffer error: EOF";
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct NefLensDataF {
|
||||
version: u32,
|
||||
exit_pupil_position: u8,
|
||||
af_aperture: u8,
|
||||
focus_position: u8,
|
||||
focus_distance: u8,
|
||||
lens_id_number: u8,
|
||||
lens_fstops: u8,
|
||||
min_focal_len: u8,
|
||||
max_focal_len: u8,
|
||||
max_aperture_at_min_focal: u8,
|
||||
max_aperture_at_max_focal: u8,
|
||||
mcu_version: u8,
|
||||
effective_max_aperture: u8,
|
||||
lens_model: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct NefLensDataZ {
|
||||
version: u32,
|
||||
pub lens_id: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum NefLensData {
|
||||
FMount(NefLensDataF),
|
||||
ZMount(NefLensDataZ),
|
||||
}
|
||||
|
||||
impl NefLensDataF {
|
||||
pub fn composite_id(&self, lens_type: u8) -> String {
|
||||
format!(
|
||||
"{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
|
||||
self.lens_id_number,
|
||||
self.lens_fstops,
|
||||
self.min_focal_len,
|
||||
self.max_focal_len,
|
||||
self.max_aperture_at_min_focal,
|
||||
self.max_aperture_at_max_focal,
|
||||
self.mcu_version,
|
||||
lens_type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_makernote(makernote: &IFD) -> Result<Option<NefLensData>> {
|
||||
if let Some(levels) = makernote.get_entry(NikonMakernote::LensData) {
|
||||
let mut buf = levels.get_data().clone();
|
||||
|
||||
let mut version: u32 = 0;
|
||||
for i in 0..4 {
|
||||
version = (version << 4) + (buf[i] - b'0') as u32;
|
||||
}
|
||||
|
||||
let lensdata = match version {
|
||||
0x100 => NefLensData::FMount(parse_lensdata_0x100(version, &buf)?),
|
||||
0x101 => NefLensData::FMount(parse_lensdata_0x101(version, &buf)?),
|
||||
0x201 | 0x202 | 0x203 => {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
NefLensData::FMount(parse_lensdata_0x101(version, &buf)?)
|
||||
}
|
||||
0x204 => {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
NefLensData::FMount(parse_lensdata_0x204(version, &buf)?)
|
||||
}
|
||||
0x400 => {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
NefLensData::FMount(parse_lensdata_0x4xx(version, &buf, 0x18a)?)
|
||||
}
|
||||
0x401 => {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
NefLensData::FMount(parse_lensdata_0x4xx(version, &buf, 0x18a)?)
|
||||
}
|
||||
0x402 => {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
NefLensData::FMount(parse_lensdata_0x4xx(version, &buf, 0x18b)?)
|
||||
}
|
||||
0x403 => {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
NefLensData::FMount(parse_lensdata_0x4xx(version, &buf, 0x2ac)?)
|
||||
}
|
||||
0x800 | 0x801 | 0x802 // Z Models
|
||||
=> {
|
||||
super::decrypt::nef_decrypt(&mut buf, 4, makernote)?;
|
||||
parse_lensdata_0x800(version, &buf)?
|
||||
}
|
||||
|
||||
_ => todo!("Lensdata version: 0x{:x} not implemented", version),
|
||||
};
|
||||
|
||||
log::debug!("NEF lens data version: 0x{:x}, lensdata: {:?}", version, lensdata);
|
||||
|
||||
Ok(Some(lensdata))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_lensdata_0x100(version: u32, buf: &[u8]) -> Result<NefLensDataF> {
|
||||
Ok(NefLensDataF {
|
||||
version,
|
||||
exit_pupil_position: 0,
|
||||
af_aperture: 0,
|
||||
focus_position: 0,
|
||||
focus_distance: 0,
|
||||
lens_id_number: *buf.get(NefLensData00::LensIDNumber as usize).ok_or(ERRMSG)?,
|
||||
lens_fstops: *buf.get(NefLensData00::LensFStops as usize).ok_or(ERRMSG)?,
|
||||
min_focal_len: *buf.get(NefLensData00::MinFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_focal_len: *buf.get(NefLensData00::MaxFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_min_focal: *buf.get(NefLensData00::MaxApertureAtMinFocal as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_max_focal: *buf.get(NefLensData00::MaxApertureAtMaxFocal as usize).ok_or(ERRMSG)?,
|
||||
mcu_version: *buf.get(NefLensData00::MCUVersion as usize).ok_or(ERRMSG)?,
|
||||
effective_max_aperture: 0,
|
||||
lens_model: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_lensdata_0x101(version: u32, buf: &[u8]) -> Result<NefLensDataF> {
|
||||
Ok(NefLensDataF {
|
||||
version,
|
||||
exit_pupil_position: *buf.get(NefLensData01::ExitPupilPosition as usize).ok_or(ERRMSG)?,
|
||||
af_aperture: *buf.get(NefLensData01::AFAperture as usize).ok_or(ERRMSG)?,
|
||||
focus_position: *buf.get(NefLensData01::FocusPosition as usize).ok_or(ERRMSG)?,
|
||||
focus_distance: *buf.get(NefLensData01::FocusDistance as usize).ok_or(ERRMSG)?,
|
||||
lens_id_number: *buf.get(NefLensData01::LensIDNumber as usize).ok_or(ERRMSG)?,
|
||||
lens_fstops: *buf.get(NefLensData01::LensFStops as usize).ok_or(ERRMSG)?,
|
||||
min_focal_len: *buf.get(NefLensData01::MinFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_focal_len: *buf.get(NefLensData01::MaxFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_min_focal: *buf.get(NefLensData01::MaxApertureAtMinFocal as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_max_focal: *buf.get(NefLensData01::MaxApertureAtMaxFocal as usize).ok_or(ERRMSG)?,
|
||||
mcu_version: *buf.get(NefLensData01::MCUVersion as usize).ok_or(ERRMSG)?,
|
||||
effective_max_aperture: *buf.get(NefLensData01::EffectiveMaxAperture as usize).ok_or(ERRMSG)?,
|
||||
lens_model: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_lensdata_0x204(version: u32, buf: &[u8]) -> Result<NefLensDataF> {
|
||||
Ok(NefLensDataF {
|
||||
version,
|
||||
exit_pupil_position: *buf.get(NefLensData204::ExitPupilPosition as usize).ok_or(ERRMSG)?,
|
||||
af_aperture: *buf.get(NefLensData204::AFAperture as usize).ok_or(ERRMSG)?,
|
||||
focus_position: *buf.get(NefLensData204::FocusPosition as usize).ok_or(ERRMSG)?,
|
||||
focus_distance: *buf.get(NefLensData204::FocusDistance as usize).ok_or(ERRMSG)?,
|
||||
lens_id_number: *buf.get(NefLensData204::LensIDNumber as usize).ok_or(ERRMSG)?,
|
||||
lens_fstops: *buf.get(NefLensData204::LensFStops as usize).ok_or(ERRMSG)?,
|
||||
min_focal_len: *buf.get(NefLensData204::MinFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_focal_len: *buf.get(NefLensData204::MaxFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_min_focal: *buf.get(NefLensData204::MaxApertureAtMinFocal as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_max_focal: *buf.get(NefLensData204::MaxApertureAtMaxFocal as usize).ok_or(ERRMSG)?,
|
||||
mcu_version: *buf.get(NefLensData204::MCUVersion as usize).ok_or(ERRMSG)?,
|
||||
effective_max_aperture: *buf.get(NefLensData204::EffectiveMaxAperture as usize).ok_or(ERRMSG)?,
|
||||
lens_model: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_lensdata_0x4xx(version: u32, buf: &[u8], model_offset: usize) -> Result<NefLensDataF> {
|
||||
let mut data = NefLensDataF { version, ..Default::default() };
|
||||
if buf.len() >= model_offset + 64 {
|
||||
let str = String::from_utf8_lossy(&buf[model_offset..model_offset + 64]);
|
||||
data.lens_model = Some(str.trim().into());
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn parse_lensdata_0x800(version: u32, buf: &[u8]) -> Result<NefLensData> {
|
||||
// This check comes from exiftool. If the buffer contains only zeros,
|
||||
// we consider the block as unused. Hopefully we find another method...
|
||||
let old_data_avail = !buf[0x04..0x04 + 16].iter().all(|&x| x == 0);
|
||||
let new_data_avail = !buf[48..].iter().all(|&x| x == 0);
|
||||
if old_data_avail {
|
||||
log::debug!("NEF lensdata 0x80X: Found old lensdata block");
|
||||
Ok(NefLensData::FMount(NefLensDataF {
|
||||
version,
|
||||
exit_pupil_position: *buf.get(NefLensData800::ExitPupilPosition as usize).ok_or(ERRMSG)?,
|
||||
af_aperture: *buf.get(NefLensData800::AFAperture as usize).ok_or(ERRMSG)?,
|
||||
focus_position: *buf.get(NefLensData800::FocusPosition as usize).ok_or(ERRMSG)?,
|
||||
focus_distance: *buf.get(NefLensData800::FocusDistance as usize).ok_or(ERRMSG)?,
|
||||
lens_id_number: *buf.get(NefLensData800::LensIDNumber as usize).ok_or(ERRMSG)?,
|
||||
lens_fstops: *buf.get(NefLensData800::LensFStops as usize).ok_or(ERRMSG)?,
|
||||
min_focal_len: *buf.get(NefLensData800::MinFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_focal_len: *buf.get(NefLensData800::MaxFocalLength as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_min_focal: *buf.get(NefLensData800::MaxApertureAtMinFocal as usize).ok_or(ERRMSG)?,
|
||||
max_aperture_at_max_focal: *buf.get(NefLensData800::MaxApertureAtMaxFocal as usize).ok_or(ERRMSG)?,
|
||||
mcu_version: *buf.get(NefLensData800::MCUVersion as usize).ok_or(ERRMSG)?,
|
||||
effective_max_aperture: *buf.get(NefLensData800::EffectiveMaxAperture as usize).ok_or(ERRMSG)?,
|
||||
lens_model: None,
|
||||
}))
|
||||
} else if new_data_avail {
|
||||
log::debug!("NEF lensdata 0x80X: Found new lensdata block");
|
||||
let mut data = NefLensDataZ { version, ..Default::default() };
|
||||
data.lens_id = LEu16(buf, 0x30);
|
||||
log::debug!("NEF lensdata 0x80X: lens_id: {}", data.lens_id);
|
||||
|
||||
Ok(NefLensData::ZMount(data))
|
||||
} else {
|
||||
Err("NEF lens data 0x80X contains neither old and new data".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(dead_code)]
|
||||
enum NefLensData00 {
|
||||
Version = 0x00,
|
||||
LensIDNumber = 0x06,
|
||||
LensFStops = 0x07,
|
||||
MinFocalLength = 0x08,
|
||||
MaxFocalLength = 0x09,
|
||||
MaxApertureAtMinFocal = 0x0a,
|
||||
MaxApertureAtMaxFocal = 0x0b,
|
||||
MCUVersion = 0x0c,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(dead_code)]
|
||||
enum NefLensData01 {
|
||||
Version = 0x00,
|
||||
ExitPupilPosition = 0x04,
|
||||
AFAperture = 0x05,
|
||||
FocusPosition = 0x08,
|
||||
FocusDistance = 0x09,
|
||||
FocalLength = 0x0a,
|
||||
LensIDNumber = 0x0b,
|
||||
LensFStops = 0x0c,
|
||||
MinFocalLength = 0x0d,
|
||||
MaxFocalLength = 0x0e,
|
||||
MaxApertureAtMinFocal = 0x0f,
|
||||
MaxApertureAtMaxFocal = 0x10,
|
||||
MCUVersion = 0x11,
|
||||
EffectiveMaxAperture = 0x12,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(dead_code)]
|
||||
enum NefLensData204 {
|
||||
Version = 0x00,
|
||||
ExitPupilPosition = 0x04,
|
||||
AFAperture = 0x05,
|
||||
FocusPosition = 0x08,
|
||||
FocusDistance = 0x0a,
|
||||
FocalLength = 0x0b,
|
||||
LensIDNumber = 0x0c,
|
||||
LensFStops = 0x0d,
|
||||
MinFocalLength = 0x0e,
|
||||
MaxFocalLength = 0x0f,
|
||||
MaxApertureAtMinFocal = 0x10,
|
||||
MaxApertureAtMaxFocal = 0x11,
|
||||
MCUVersion = 0x12,
|
||||
EffectiveMaxAperture = 0x13,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(dead_code)]
|
||||
enum NefLensData800 {
|
||||
Version = 0x00,
|
||||
OldLensDataFlag = 0x03,
|
||||
ExitPupilPosition = 0x04,
|
||||
AFAperture = 0x05,
|
||||
FocusPosition = 0x09,
|
||||
FocusDistance = 0x0b,
|
||||
FocalLength = 0x0c,
|
||||
LensIDNumber = 0x0d,
|
||||
LensFStops = 0x0e,
|
||||
MinFocalLength = 0x0f,
|
||||
MaxFocalLength = 0x10,
|
||||
MaxApertureAtMinFocal = 0x11,
|
||||
MaxApertureAtMaxFocal = 0x12,
|
||||
MCUVersion = 0x13,
|
||||
EffectiveMaxAperture = 0x14,
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
use super::{Camera, Decoder, FormatHint, RawDecodeParams, RawMetadata, ok_cfa_image};
|
||||
use crate::Result;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::exif::Exif;
|
||||
use crate::packed::{decode_10le_lsb16, decode_12be_msb16, decode_12le_16bitaligned};
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::{RawImage, RawLoader, RawlerError};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NakedDecoder<'a> {
|
||||
#[allow(dead_code)]
|
||||
rawloader: &'a RawLoader,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> NakedDecoder<'a> {
|
||||
pub fn new(camera: Camera, rawloader: &'a RawLoader) -> Result<NakedDecoder<'a>> {
|
||||
Ok(NakedDecoder { rawloader, camera })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for NakedDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let data = file.as_vec()?;
|
||||
let buffer = &data;
|
||||
let width = self.camera.raw_width;
|
||||
let height = self.camera.raw_height;
|
||||
let size = self.camera.filesize;
|
||||
let bits = size * 8 / width / height;
|
||||
|
||||
let image = if self.camera.find_hint("12le_16bitaligned") {
|
||||
decode_12le_16bitaligned(buffer, width, height, dummy)
|
||||
} else {
|
||||
match bits {
|
||||
10 => decode_10le_lsb16(buffer, width, height, dummy),
|
||||
12 => decode_12be_msb16(buffer, width, height, dummy),
|
||||
_ => return Err(RawlerError::unsupported(&self.camera, format!("Naked: Don't know about {} bps images", bits))),
|
||||
}
|
||||
};
|
||||
let cpp = 1;
|
||||
ok_cfa_image(self.camera.clone(), cpp, [f32::NAN, f32::NAN, f32::NAN, f32::NAN], image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::default();
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::Unknown
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/// The Nef decoder can be used for NRW files, too,
|
||||
/// so this is just an type alias.
|
||||
pub type NrwDecoder<'a> = super::nef::NefDecoder<'a>;
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
use std::cmp;
|
||||
use std::io::Read;
|
||||
use std::io::Seek;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::buffer::PaddedBuf;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Rational;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::packed::*;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::pumps::BitPump;
|
||||
use crate::pumps::BitPumpMSB;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
|
||||
const MFT_MOUNT: &str = "MFT-mount";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OrfDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
camera: Camera,
|
||||
makernote: IFD,
|
||||
}
|
||||
|
||||
pub fn parse_makernote<R: Read + Seek>(reader: &mut R, exif_ifd: &IFD) -> Result<Option<IFD>> {
|
||||
if let Some(exif) = exif_ifd.get_entry(ExifTag::MakerNotes) {
|
||||
let offset = exif.offset().unwrap() as u32;
|
||||
log::debug!("Makernote offset: {}", offset);
|
||||
match &exif.value {
|
||||
Value::Undefined(data) => {
|
||||
let mut off = 0;
|
||||
// Olympus starts the makernote with their own name, sometimes truncated
|
||||
if data[0..5] == b"OLYMP"[..] {
|
||||
off += 8;
|
||||
if data[0..7] == b"OLYMPUS"[..] {
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
// OM Digital Solutions put their name in front of the TIFF structure, too
|
||||
if data[0..9] == b"OM SYSTEM"[..] {
|
||||
off += 16;
|
||||
assert_eq!(data[12..14], b"II"[..]);
|
||||
}
|
||||
let endian = exif_ifd.endian;
|
||||
//assert!(data[off..off + 2] == b"II"[..] || data[off..off + 2] == b"MM"[..], "ORF: must contain endian marker in makernote IFD");
|
||||
//let endian = if data[off..off + 2] == b"II"[..] { Endian::Little } else { Endian::Big };
|
||||
//off += 4;
|
||||
|
||||
let mut mainifd = IFD::new(reader, offset + off as u32, exif_ifd.base, exif_ifd.corr, endian, &[0x3000])?;
|
||||
|
||||
// Parse the Olympus Equipment section if it exists
|
||||
if let Some(entry) = mainifd.get_entry_raw_with_len(OrfMakernotes::EquipmentIFD, reader, 4)? {
|
||||
// The entry is of type UNDEFINED and count = 1. This tag contains a single 32 bit
|
||||
// offset to the IFD.
|
||||
let ioff = entry.get_force_u32(0);
|
||||
log::debug!("Found EquipmentIFD at offset: {}", ioff);
|
||||
// The IFD start at offset+ioff, but all offsets inside the IFD a relative to the main makernote IFD offset.
|
||||
// So we use the main IFD as base offset, but start parsing IFD at ioff.
|
||||
let ifd = IFD::new(reader, ioff, offset, 0, endian, &[])?;
|
||||
mainifd.sub.insert(OrfMakernotes::EquipmentIFD.into(), vec![ifd]);
|
||||
}
|
||||
|
||||
// For Olympus or OM-System models
|
||||
if off == 12 || off == 16 {
|
||||
// Parse the Olympus ImgProc section if it exists
|
||||
let ioff = if let Some(entry) = mainifd.get_entry_raw_with_len(OrfMakernotes::ImageProcessingIFD, reader, 4)? {
|
||||
// The entry is of type UNDEFINED and count = 1. This tag contains a single 32 bit
|
||||
// offset to the IFD.
|
||||
entry.get_force_u32(0)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if ioff != 0 {
|
||||
log::debug!("Found ImageIFD at offset: {}", ioff);
|
||||
// The IFD start at offset+ioff, but all offsets inside the IFD a relative to the main makernote IFD offset.
|
||||
// So we use the main IFD as base offset, but start parsing IFD at ioff.
|
||||
let iprocifd = IFD::new(reader, ioff, offset, 0, endian, &[])?;
|
||||
mainifd.sub.insert(OrfMakernotes::ImageProcessingIFD.into(), vec![iprocifd]);
|
||||
} else {
|
||||
log::debug!("ORF ImageIFD not found");
|
||||
}
|
||||
}
|
||||
Ok(Some(mainifd))
|
||||
}
|
||||
_ => Err(RawlerError::DecoderFailed("EXIF makernote has unknown type".to_string())),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OrfDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<OrfDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
parse_makernote(&mut file.reader(), exif)?
|
||||
} else {
|
||||
log::warn!("ORF makernote not found");
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
|
||||
//makernote.dump::<ExifTag>(0).iter().for_each(|line| eprintln!("DUMP: {}", line));
|
||||
|
||||
Ok(OrfDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
makernote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for OrfDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with StripOffsets tag")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let counts = fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts);
|
||||
let bps = match self.get_bits_per_pixel()? {
|
||||
Some(bps) if [12, 14].contains(&bps) => bps,
|
||||
Some(bps) => {
|
||||
log::warn!("Unsupported bps: {}", bps);
|
||||
bps
|
||||
}
|
||||
None => {
|
||||
log::debug!("No bps found, fallback to 12");
|
||||
12
|
||||
}
|
||||
} as usize;
|
||||
|
||||
let mut size: usize = 0;
|
||||
for i in 0..counts.count() {
|
||||
size += counts.force_u32(i as usize) as usize;
|
||||
}
|
||||
|
||||
let camera = if width >= self.camera.highres_width {
|
||||
self.rawloader.check_supported_with_mode(self.tiff.root_ifd(), "highres")?
|
||||
} else {
|
||||
self.camera.clone()
|
||||
};
|
||||
|
||||
let src = file.subview_padded(offset as u64, size as u64)?; // TODO add size and check all samples
|
||||
|
||||
log::debug!(
|
||||
"ORF raw image size: {}, dim: {}x{}, total mp: {}, strip counts: {}",
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
width * height,
|
||||
counts.count()
|
||||
);
|
||||
|
||||
// These conditions are sorted in descending order.
|
||||
// All ORF files comes with no hints about the used compression.
|
||||
// But we need to differentiate between 12be-interlaced and
|
||||
// 12be-msb32 because they are in the same size range.
|
||||
let image = if size >= width * height * 2 {
|
||||
if self.tiff.little_endian() {
|
||||
log::debug!("ORF: decode_12le_unpacked_left_aligned");
|
||||
decode_12le_unpacked_left_aligned(&src, width, height, dummy)
|
||||
} else {
|
||||
log::debug!("ORF: decode_12be_unpacked_left_aligned");
|
||||
decode_12be_unpacked_left_aligned(&src, width, height, dummy)
|
||||
}
|
||||
} else if size >= width * height / 10 * 16 {
|
||||
log::debug!("ORF: decode_12le_wcontrol");
|
||||
decode_12le_wcontrol(&src, width, height, dummy)
|
||||
} else if size >= width * height * 12 / 8 {
|
||||
if self.camera.find_hint("interlaced") {
|
||||
log::debug!("ORF: decode_12be_interlaced");
|
||||
decode_12be_interlaced(&src, width, height, dummy)
|
||||
} else {
|
||||
log::debug!("ORF: decode_12be_msb32");
|
||||
//decode_12be_interlaced(&src, width, height, dummy)
|
||||
decode_12be_msb32(&src, width, height, dummy)
|
||||
}
|
||||
} else {
|
||||
log::debug!("ORF: fallback to decode_compressed");
|
||||
OrfDecoder::decode_compressed(&src, width, height, bps, dummy)
|
||||
};
|
||||
|
||||
let cpp = 1;
|
||||
|
||||
let blacklevel = self.get_blacklevel(bps)?;
|
||||
let whitelevel = None;
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera));
|
||||
let mut img = RawImage::new(camera, image, cpp, normalize_wb(self.get_wb()?), photometric, blacklevel, whitelevel, dummy);
|
||||
if let Some(crop) = self.get_crop()? {
|
||||
img.crop_area = Some(crop);
|
||||
}
|
||||
if bps == 14 {
|
||||
// Blacklevel is already corrected, only required for whitelevel.
|
||||
// Encoded for 12 bps, whitelevel must be multiplied by 4.
|
||||
img.whitelevel.0.iter_mut().for_each(|level| *level = *level << 2);
|
||||
}
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, __params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::ORF
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OrfDecoder<'a> {
|
||||
/* This is probably the slowest decoder of them all.
|
||||
* I cannot see any way to effectively speed up the prediction
|
||||
* phase, which is by far the slowest part of this algorithm.
|
||||
* Also there is no way to multithread this code, since prediction
|
||||
* is based on the output of all previous pixel (bar the first four)
|
||||
*/
|
||||
|
||||
pub fn decode_compressed(buf: &PaddedBuf, width: usize, height: usize, bps: usize, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
|
||||
/* Build a table to quickly look up "high" value */
|
||||
let mut bittable: [u8; 4096] = [0; 4096];
|
||||
for i in 0..4096 {
|
||||
let mut b = 12;
|
||||
for high in 0..12 {
|
||||
if ((i >> (11 - high)) & 1) != 0 {
|
||||
b = high;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bittable[i] = b;
|
||||
}
|
||||
|
||||
let mut left: [i32; 2] = [0; 2];
|
||||
let mut nw: [i32; 2] = [0; 2];
|
||||
let skip = if bps == 14 { 8 } else { 7 };
|
||||
let mut pump = BitPumpMSB::new(&buf[skip..]);
|
||||
|
||||
for row in 0..height {
|
||||
let mut acarry: [[i32; 3]; 2] = [[0; 3]; 2];
|
||||
|
||||
for c in 0..width / 2 {
|
||||
let col: usize = c * 2;
|
||||
for s in 0..2 {
|
||||
// Run twice for odd and even pixels
|
||||
let i = if acarry[s][2] < 3 { 2 } else { 0 };
|
||||
let mut nbits = 2 + i;
|
||||
while ((acarry[s][0] >> (nbits + i)) & 0xffff) > 0 {
|
||||
nbits += 1
|
||||
}
|
||||
nbits = cmp::min(nbits, 16);
|
||||
let b = pump.peek_ibits(15);
|
||||
|
||||
let sign: i32 = -(b >> 14);
|
||||
let low: i32 = (b >> 12) & 3;
|
||||
let mut high: i32 = bittable[(b & 4095) as usize] as i32;
|
||||
|
||||
// Skip bytes used above or read bits
|
||||
if high == 12 {
|
||||
pump.consume_bits(15);
|
||||
high = pump.get_ibits(16 - nbits) >> 1;
|
||||
} else {
|
||||
pump.consume_bits((high + 4) as u32);
|
||||
}
|
||||
|
||||
acarry[s][0] = ((high << nbits) | pump.get_ibits(nbits)) as i32;
|
||||
let diff = (acarry[s][0] ^ sign) + acarry[s][1];
|
||||
acarry[s][1] = (diff * 3 + acarry[s][1]) >> 5;
|
||||
acarry[s][2] = if acarry[s][0] > 16 { 0 } else { acarry[s][2] + 1 };
|
||||
|
||||
if row < 2 || col < 2 {
|
||||
// We're in a border, special care is needed
|
||||
let pred = if row < 2 && col < 2 {
|
||||
// We're in the top left corner
|
||||
0
|
||||
} else if row < 2 {
|
||||
// We're going along the top border
|
||||
left[s]
|
||||
} else {
|
||||
// col < 2, we're at the start of a line
|
||||
nw[s] = out[(row - 2) * width + (col + s)] as i32;
|
||||
nw[s]
|
||||
};
|
||||
left[s] = pred + ((diff << 2) | low);
|
||||
out[row * width + (col + s)] = left[s] as u16;
|
||||
} else {
|
||||
let up: i32 = out[(row - 2) * width + (col + s)] as i32;
|
||||
let left_minus_nw: i32 = left[s] - nw[s];
|
||||
let up_minus_nw: i32 = up - nw[s];
|
||||
// Check if sign is different, and one is not zero
|
||||
let pred = if left_minus_nw * up_minus_nw < 0 {
|
||||
if left_minus_nw.abs() > 32 || up_minus_nw.abs() > 32 {
|
||||
left[s] + up_minus_nw
|
||||
} else {
|
||||
(left[s] + up) >> 1
|
||||
}
|
||||
} else if left_minus_nw.abs() > up_minus_nw.abs() {
|
||||
left[s]
|
||||
} else {
|
||||
up
|
||||
};
|
||||
|
||||
left[s] = pred + ((diff << 2) | low);
|
||||
nw[s] = up;
|
||||
out[row * width + (col + s)] = left[s] as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn get_blacklevel(&self, bps: usize) -> Result<Option<BlackLevel>> {
|
||||
let ifd = self.makernote.find_ifds_with_tag(OrfImageProcessing::OrfBlackLevels);
|
||||
if ifd.is_empty() {
|
||||
log::info!("ORF: Couldn't find ImgProc IFD, unable to read blacklevel");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let blacks = fetch_tiff_tag!(ifd[0], OrfImageProcessing::OrfBlackLevels);
|
||||
let mut levels = [blacks.force_u16(0), blacks.force_u16(1), blacks.force_u16(2), blacks.force_u16(3)];
|
||||
if bps == 14 {
|
||||
// Blacklevel is encoded for 12 bits
|
||||
levels.iter_mut().for_each(|level| *level = *level << 2);
|
||||
}
|
||||
Ok(Some(BlackLevel::new(&levels, self.camera.cfa.width, self.camera.cfa.height, 1)))
|
||||
}
|
||||
|
||||
fn get_bits_per_pixel(&self) -> Result<Option<u16>> {
|
||||
let ifd = self.makernote.find_ifds_with_tag(OrfImageProcessing::ValidBits);
|
||||
if ifd.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(fetch_tiff_tag!(ifd[0], OrfImageProcessing::ValidBits).force_u16(0)))
|
||||
}
|
||||
|
||||
fn get_crop(&self) -> Result<Option<Rect>> {
|
||||
let ifd = self.makernote.find_ifds_with_tag(OrfImageProcessing::CropLeft);
|
||||
if ifd.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let crop_left = fetch_tiff_tag!(ifd[0], OrfImageProcessing::CropLeft).force_usize(0);
|
||||
let crop_top = fetch_tiff_tag!(ifd[0], OrfImageProcessing::CropTop).force_usize(0);
|
||||
let crop_width = fetch_tiff_tag!(ifd[0], OrfImageProcessing::CropWidth).force_usize(0);
|
||||
let crop_height = fetch_tiff_tag!(ifd[0], OrfImageProcessing::CropHeight).force_usize(0);
|
||||
Ok(Some(Rect::new(Point::new(crop_left, crop_top), Dim2::new(crop_width, crop_height))))
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
if let Some(ifd) = self.makernote.get_sub_ifd(OrfMakernotes::EquipmentIFD) {
|
||||
match ifd.get_entry(OrfEquipmentTags::LensType) {
|
||||
Some(Entry {
|
||||
value: Value::Byte(settings), ..
|
||||
}) => {
|
||||
log::debug!("Lens type tag: {:?}", settings);
|
||||
let make_id = settings[0];
|
||||
let model_id = settings[2];
|
||||
let submodel_id = settings[3];
|
||||
let composite_id = format!("{:02X} {:02X} {:02X}", make_id, model_id, submodel_id);
|
||||
log::debug!("ORF lens composite ID: {}", composite_id);
|
||||
let resolver = LensResolver::new()
|
||||
.with_olympus_id(Some(composite_id))
|
||||
.with_camera(&self.camera)
|
||||
.with_focal_len(self.get_focal_len()?)
|
||||
.with_mounts(&[MFT_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Camera settings in makernote not found, no lens data available");
|
||||
}
|
||||
}
|
||||
}
|
||||
log::warn!("No lens data found");
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_focal_len(&self) -> Result<Option<Rational>> {
|
||||
if let Some(exif) = self.tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
if let Some(Entry {
|
||||
value: Value::Short(focal), ..
|
||||
}) = exif.get_entry(ExifTag::FocalLength)
|
||||
{
|
||||
return Ok(focal.get(1).map(|v| Rational::new(*v as u32, 1)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let redmul = self.makernote.get_entry(OrfMakernotes::OlympusRedMul);
|
||||
let bluemul = self.makernote.get_entry(OrfMakernotes::OlympusBlueMul);
|
||||
match (redmul, bluemul) {
|
||||
(Some(redmul), Some(bluemul)) => Ok([redmul.force_u32(0) as f32, 256.0, 256.0, bluemul.force_u32(0) as f32]),
|
||||
_ => {
|
||||
let ifd = self.makernote.find_ifds_with_tag(OrfImageProcessing::OrfBlackLevels);
|
||||
if ifd.is_empty() {
|
||||
return Err(RawlerError::DecoderFailed("ORF: Couldn't find ImgProc IFD".to_string()));
|
||||
}
|
||||
let wbs = fetch_tiff_tag!(ifd[0], OrfImageProcessing::WB_RBLevels);
|
||||
Ok([wbs.force_f32(0), 256.0, 256.0, wbs.force_f32(1)])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
log::debug!("ORF raw wb: {:?}", raw_wb);
|
||||
let div = raw_wb[1];
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(OrfMakernotes);
|
||||
crate::tags::tiff_tag_enum!(OrfImageProcessing);
|
||||
crate::tags::tiff_tag_enum!(OrfEquipmentTags);
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum OrfMakernotes {
|
||||
ImageProcessingIFD = 0x2040,
|
||||
RawInfo = 0x3000,
|
||||
OlympusRedMul = 0x1017,
|
||||
OlympusBlueMul = 0x1018,
|
||||
EquipmentIFD = 0x2010,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum OrfImageProcessing {
|
||||
ImageProcessingVersion = 0x0000,
|
||||
WB_RBLevels = 0x0100,
|
||||
OrfBlackLevels = 0x0600,
|
||||
ValidBits = 0x0611,
|
||||
CropLeft = 0x0612,
|
||||
CropTop = 0x0613,
|
||||
CropWidth = 0x0614,
|
||||
CropHeight = 0x0615,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum OrfEquipmentTags {
|
||||
LensType = 0x0201,
|
||||
}
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
use image::DynamicImage;
|
||||
use log::debug;
|
||||
use log::warn;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image_ok;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::Endian;
|
||||
use crate::decompressors::ljpeg::huffman::*;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::packed::*;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::pumps::BitPumpMSB;
|
||||
use crate::pumps::ByteStream;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PefDecoder<'a> {
|
||||
camera: Camera,
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: IFD,
|
||||
/// Offset of makernote, needed to correct offsets of preview image
|
||||
makernote_offset: u32,
|
||||
}
|
||||
|
||||
impl<'a> PefDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<PefDecoder<'a>> {
|
||||
debug!("PEF decoder choosen");
|
||||
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::Absolute, &[])?
|
||||
} else {
|
||||
warn!("PEF makernote not found");
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
|
||||
let makernote_offset = tiff
|
||||
.find_first_ifd_with_tag(ExifTag::MakerNotes)
|
||||
.and_then(|exif| exif.get_entry(ExifTag::MakerNotes))
|
||||
.map(|entry| entry.offset().unwrap() as u32)
|
||||
.unwrap_or(0);
|
||||
|
||||
//eprintln!("IFD makernote:");
|
||||
//for line in makernote.dump::<PefMakernote>(10) {
|
||||
// eprintln!("{}", line);
|
||||
//}
|
||||
|
||||
Ok(PefDecoder {
|
||||
camera,
|
||||
tiff,
|
||||
rawloader,
|
||||
makernote,
|
||||
makernote_offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// CR2 format encapsulation for analyzer
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PefFormat {
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl<'a> Decoder for PefDecoder<'a> {
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
FormatDump::Pef(PefFormat { tiff: self.tiff.clone() })
|
||||
}
|
||||
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
//for (i, ifd) in self.tiff.chains().iter().enumerate() {
|
||||
// eprintln!("IFD {}", i);
|
||||
// for line in ifd.dump::<crate::tags::LegacyTiffRootTag>(10) {
|
||||
// eprintln!("{}", line);
|
||||
// }
|
||||
//}
|
||||
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| RawlerError::unsupported(&self.camera, "Unable to find IFD"))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
|
||||
let image = match fetch_tiff_tag!(raw, TiffCommonTag::Compression).get_u32(0) {
|
||||
Ok(Some(1)) => decode_16be(src, width, height, dummy),
|
||||
Ok(Some(32773)) => decode_12be(src, width, height, dummy),
|
||||
Ok(Some(65535)) => self.decode_compressed(src, width, height, dummy)?,
|
||||
Ok(Some(c)) => return Err(RawlerError::unsupported(&self.camera, format!("PEF: Don't know how to read compression {}", c))),
|
||||
_ => return Err(RawlerError::unsupported(&self.camera, "PEF: No compression tag found")),
|
||||
};
|
||||
|
||||
let cpp = 1;
|
||||
let wb = self.get_wb()?;
|
||||
let blacklevel = self.get_blacklevel()?;
|
||||
let whitelevel = None;
|
||||
debug!("Found WB: {:?}", wb);
|
||||
let photometric = if self.camera.cfa.is_valid() {
|
||||
RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera))
|
||||
} else {
|
||||
RawPhotometricInterpretation::LinearRaw
|
||||
};
|
||||
Ok(RawImage::new(self.camera.clone(), image, cpp, wb, photometric, blacklevel, whitelevel, dummy))
|
||||
}
|
||||
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let size = self.makernote.get_entry(PefMakernote::PreviewImageSize);
|
||||
let length = self.makernote.get_entry(PefMakernote::PreviewImageLength);
|
||||
let start = self.makernote.get_entry(PefMakernote::PreviewImageStart);
|
||||
|
||||
let image = match (size, length, start) {
|
||||
(Some(size), Some(length), Some(start)) => {
|
||||
let _width = size.force_u16(0);
|
||||
let _height = size.force_u16(1);
|
||||
let len = length.force_u32(0);
|
||||
let offset = start.force_u32(0);
|
||||
if len > 0 && offset > 0 {
|
||||
let buf = file.subview((self.makernote_offset + offset) as u64, len as u64)?;
|
||||
match image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg) {
|
||||
Ok(img) => Some(img),
|
||||
Err(_) => {
|
||||
// Test offset without correction
|
||||
let buf = file.subview(offset as u64, len as u64)?;
|
||||
let img = image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to read JPEG: {:?}", err)))?;
|
||||
Some(img)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
if let Some(image) = image {
|
||||
// This tag contains the border definitions for the preview image.
|
||||
// We cut away these black borders.
|
||||
if let Some(Entry {
|
||||
value: Value::Byte(borders), ..
|
||||
}) = self.makernote.get_entry(PefMakernote::PreviewImageBorders)
|
||||
{
|
||||
let y = borders[0] as u32;
|
||||
let x = borders[2] as u32;
|
||||
let width = image.width() - x - borders[3] as u32;
|
||||
let height = image.height() - y - borders[1] as u32;
|
||||
return Ok(Some(image.crop_imm(x, y, width, height)));
|
||||
} else {
|
||||
return Ok(Some(image));
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::PEF
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PefDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
match self.makernote.get_entry(PefMakernote::WhitePoint) {
|
||||
Some(wb) => {
|
||||
let raw_wb = [wb.force_u16(0) as f32, wb.force_u16(1) as f32, wb.force_u16(2) as f32, wb.force_u16(3) as f32];
|
||||
Ok(normalize_wb(raw_wb))
|
||||
}
|
||||
None => Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN]),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_blacklevel(&self) -> Result<Option<BlackLevel>> {
|
||||
match self.makernote.get_entry(PefMakernote::BlackPoint) {
|
||||
Some(data) => {
|
||||
if self.camera.cfa.is_valid() {
|
||||
let levels = [data.force_u16(0), data.force_u16(1), data.force_u16(2), data.force_u16(3)];
|
||||
Ok(Some(BlackLevel::new(&levels, self.camera.cfa.width, self.camera.cfa.height, 1)))
|
||||
} else {
|
||||
// Monochrome PEF like K-3
|
||||
let levels = [data.force_u16(0)];
|
||||
Ok(Some(BlackLevel::new(&levels, 1, 1, 1)))
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
if let Some(Entry {
|
||||
value: Value::Byte(settings), ..
|
||||
}) = self.makernote.get_entry(PefMakernote::LensRec)
|
||||
{
|
||||
let lens_id = (settings[0] as u32, settings[1] as u32);
|
||||
debug!("LensRec tag: {:?}", lens_id);
|
||||
if [0, 1, 2].contains(&lens_id.0) {
|
||||
// 0 = M-42 or no lens
|
||||
// 1 = K or M lens
|
||||
// 2 = A Series lens
|
||||
return Ok(None);
|
||||
} else {
|
||||
let resolver = LensResolver::new()
|
||||
.with_camera(&self.camera)
|
||||
.with_lens_id(lens_id)
|
||||
.with_mounts(&["k-mount".into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn decode_compressed(&self, src: &[u8], width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
if let Some(huff) = self.makernote.get_entry(PefMakernote::HuffmanTable) {
|
||||
match &huff.value {
|
||||
Value::Undefined(data) => Self::do_decode(src, Some((data, self.tiff.get_endian())), width, height, dummy),
|
||||
_ => todo!(), // should not happen!
|
||||
}
|
||||
} else {
|
||||
Self::do_decode(src, None, width, height, dummy)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn do_decode(src: &[u8], huff: Option<(&[u8], Endian)>, width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
let mut htable = HuffTable::empty();
|
||||
|
||||
/* Attempt to read huffman table, if found in makernote */
|
||||
if let Some((huff, endian)) = huff {
|
||||
debug!("Use in-file Huffman table");
|
||||
let mut stream = ByteStream::new(huff, endian);
|
||||
|
||||
let depth: usize = (stream.get_u16() as usize + 12) & 0xf;
|
||||
stream.consume_bytes(12);
|
||||
|
||||
let mut v0: [u32; 16] = [0; 16];
|
||||
for i in 0..depth {
|
||||
v0[i] = stream.get_u16() as u32;
|
||||
}
|
||||
|
||||
let mut v1: [u32; 16] = [0; 16];
|
||||
for i in 0..depth {
|
||||
v1[i] = stream.get_u8() as u32;
|
||||
}
|
||||
|
||||
// Calculate codes and store bitcounts
|
||||
let mut v2: [u32; 16] = [0; 16];
|
||||
for c in 0..depth {
|
||||
v2[c] = v0[c] >> (12 - v1[c]);
|
||||
htable.bits[v1[c] as usize] += 1;
|
||||
}
|
||||
|
||||
// Find smallest
|
||||
for i in 0..depth {
|
||||
let mut sm_val: u32 = 0xfffffff;
|
||||
let mut sm_num: u32 = 0xff;
|
||||
for j in 0..depth {
|
||||
if v2[j] <= sm_val {
|
||||
sm_num = j as u32;
|
||||
sm_val = v2[j];
|
||||
}
|
||||
}
|
||||
htable.huffval[i] = sm_num;
|
||||
v2[sm_num as usize] = 0xffffffff;
|
||||
}
|
||||
} else {
|
||||
debug!("Fallback to standard Huffman table");
|
||||
// Initialize with legacy data
|
||||
let pentax_tree: [u8; 29] = [0, 2, 3, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 3, 4, 2, 5, 1, 6, 0, 7, 8, 9, 10, 11, 12];
|
||||
let mut acc: usize = 0;
|
||||
for i in 0..16 {
|
||||
htable.bits[i + 1] = pentax_tree[i] as u32;
|
||||
acc += htable.bits[i + 1] as usize;
|
||||
}
|
||||
for i in 0..acc {
|
||||
htable.huffval[i] = pentax_tree[i + 16] as u32;
|
||||
}
|
||||
}
|
||||
|
||||
htable.initialize()?;
|
||||
|
||||
let mut pump = BitPumpMSB::new(src);
|
||||
let mut pred_up1: [i32; 2] = [0, 0];
|
||||
let mut pred_up2: [i32; 2] = [0, 0];
|
||||
let mut pred_left1: i32;
|
||||
let mut pred_left2: i32;
|
||||
|
||||
for row in 0..height {
|
||||
pred_up1[row & 1] += htable.huff_decode(&mut pump)?;
|
||||
pred_up2[row & 1] += htable.huff_decode(&mut pump)?;
|
||||
pred_left1 = pred_up1[row & 1];
|
||||
pred_left2 = pred_up2[row & 1];
|
||||
out[row * width + 0] = pred_left1 as u16;
|
||||
out[row * width + 1] = pred_left2 as u16;
|
||||
for col in (2..width).step_by(2) {
|
||||
pred_left1 += htable.huff_decode(&mut pump)?;
|
||||
pred_left2 += htable.huff_decode(&mut pump)?;
|
||||
out[row * width + col + 0] = pred_left1 as u16;
|
||||
out[row * width + col + 1] = pred_left2 as u16;
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
debug!("PEF raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1];
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(PefMakernote);
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum PefMakernote {
|
||||
PentaxVersion = 0x0000,
|
||||
PentaxModelType = 0x0001,
|
||||
PreviewImageSize = 0x0002,
|
||||
PreviewImageLength = 0x0003,
|
||||
PreviewImageStart = 0x0004,
|
||||
PentaxModelId = 0x0005,
|
||||
Date = 0x0006,
|
||||
Time = 0x0007,
|
||||
Quality = 0x0008,
|
||||
PentaxImageSize = 0x0009,
|
||||
PictureMode = 0x000b,
|
||||
FlashMode = 0x000c,
|
||||
FocusMode = 0x000d,
|
||||
AFPointSelected = 0x000e,
|
||||
AFPointsInFocus = 0x000f,
|
||||
FocusPosition = 0x0010,
|
||||
ExposureTime = 0x0012,
|
||||
FNumber = 0x0013,
|
||||
ISO = 0x0014,
|
||||
LightReading = 0x0015,
|
||||
ExposureCompensation = 0x0016,
|
||||
MeteringMode = 0x0017,
|
||||
AutoBracketing = 0x0018,
|
||||
WhiteBalance = 0x0019,
|
||||
WhiteBalanceMode = 0x001a,
|
||||
BlueBalance = 0x001b,
|
||||
RedBalance = 0x001c,
|
||||
FocalLength = 0x001d,
|
||||
DigitalZoom = 0x001e,
|
||||
Saturation = 0x001f,
|
||||
Contrast = 0x0020,
|
||||
Sharpness = 0x0021,
|
||||
WordTimeLocation = 0x0022,
|
||||
HometownCity = 0x0023,
|
||||
DestinationCity = 0x0024,
|
||||
HometownDST = 0x0025,
|
||||
DestinationDST = 0x0026,
|
||||
DSPFirmwareVersion = 0x0027,
|
||||
CPUFirmwareVersion = 0x0028,
|
||||
FrameNumber = 0x0029,
|
||||
EffectiveLV = 0x002d,
|
||||
ImageEditing = 0x0032,
|
||||
PictureMode2 = 0x0033,
|
||||
DriveMode = 0x0034,
|
||||
SensorSize = 0x0035,
|
||||
ColorSpace = 0x0037,
|
||||
ImageAreaOffset = 0x0038,
|
||||
RawImageSize = 0x0039,
|
||||
AFPointsInFocus2 = 0x003c,
|
||||
DataScaling = 0x003d,
|
||||
PreviewImageBorders = 0x003e,
|
||||
LensRec = 0x003f,
|
||||
SensitivityAdjust = 0x0040,
|
||||
ImageEditCount = 0x0041,
|
||||
CameraTemerature = 0x0047,
|
||||
AELock = 0x0048,
|
||||
NoiseReduction = 0x0049,
|
||||
FlashExposureComp = 0x004d,
|
||||
ImageTone = 0x004f,
|
||||
ColorTemperature = 0x0050,
|
||||
ColorTempDaylight = 0x0053,
|
||||
ColorTempShade = 0x0054,
|
||||
ColorTempCloudy = 0x0055,
|
||||
ColorTempTungsten = 0x0056,
|
||||
ColorTempFluorescentD = 0x0057,
|
||||
ColorTempFluorescentN = 0x0058,
|
||||
ColorTempFluorescentW = 0x0059,
|
||||
ColorTempFlash = 0x005a,
|
||||
ShakeReductionInfo = 0x005c,
|
||||
ShutterCount = 0x005d,
|
||||
FaceInfo = 0x0060,
|
||||
RawDevelopmentProcess = 0x0062,
|
||||
Hue = 0x0067,
|
||||
AWBInfo = 0x0068,
|
||||
DynamicRangeExpansion = 0x0069,
|
||||
TimeInfo = 0x006b,
|
||||
HighLowKeyAdj = 0x006c,
|
||||
ContastHighlight = 0x006d,
|
||||
ContrastShadow = 0x006e,
|
||||
ConstrastHightlightShadowAdj = 0x006f,
|
||||
FineSharpness = 0x0070,
|
||||
HighISONoiseReduction = 0x0071,
|
||||
AFAdjustment = 0x0072,
|
||||
MonochromeFilterEffect = 0x0073,
|
||||
MonochromeToning = 0x0074,
|
||||
FaceDetect = 0x0076,
|
||||
FaceDetectFrameIsze = 0x0077,
|
||||
ShadowCorrection = 0x0079,
|
||||
ISOAutoParameters = 0x007a,
|
||||
CrossProcess = 0x007b,
|
||||
LensCorr = 0x007d,
|
||||
WhiteLevel = 0x007e,
|
||||
BleachBypassToning = 0x007f,
|
||||
AspectRatio = 0x0080,
|
||||
BlurControl = 0x0082,
|
||||
HDR = 0x0085,
|
||||
ShutterType = 0x0087,
|
||||
NeutralDensityFilter = 0x0088,
|
||||
ISO2 = 0x008b,
|
||||
IntervalShooting = 0x0092,
|
||||
SkinToneCorrection = 0x0095,
|
||||
ClarityControl = 0x0096,
|
||||
BlackPoint = 0x0200,
|
||||
WhitePoint = 0x0201,
|
||||
ColorMatrixA = 0x0203,
|
||||
ColorMatrixB = 0x0204,
|
||||
CameraSettings = 0x0205,
|
||||
AEInfo = 0x0206,
|
||||
LensInfo = 0x0207,
|
||||
FlashInfo = 0x0208,
|
||||
AEMeteringSegements = 0x0209,
|
||||
FlashMeteringSegements = 0x020a,
|
||||
SlaveFlashMeteringSegements = 0x020b,
|
||||
WB_RGGBLevelsDaylight = 0x020d,
|
||||
WB_RGGBLevelsShade = 0x020e,
|
||||
WB_RGGBLevelsCloudy = 0x020f,
|
||||
WB_RGGBLevelsTungsten = 0x0210,
|
||||
WB_RGGBLevelsFluorescentD = 0x0211,
|
||||
WB_RGGBLevelsFluorescentN = 0x0212,
|
||||
WB_RGGBLevelsFluorescentW = 0x0213,
|
||||
WB_RGGBLevelsFlash = 0x0214,
|
||||
CameraInfo = 0x0215,
|
||||
BatteryInfo = 0x0216,
|
||||
SaturationInfo = 0x021b,
|
||||
ColorMatrixA2 = 0x021c,
|
||||
ColorMatrixB2 = 0x021d,
|
||||
AFInfo = 0x021f,
|
||||
HuffmanTable = 0x0220,
|
||||
KelvinWB = 0x0221,
|
||||
ColorInfo = 0x0222,
|
||||
EVStepInfo = 0x0224,
|
||||
ShotInfo = 0x0226,
|
||||
FacePos = 0x0227,
|
||||
FaceSize = 0x0228,
|
||||
SerialNumber = 0x0229,
|
||||
FilterInfo = 0x022a,
|
||||
LevelInfo = 0x022b,
|
||||
WBLevels = 0x022d,
|
||||
Artist = 0x022e,
|
||||
Copyright = 0x022f,
|
||||
FirmwareVersion = 0x0230,
|
||||
ConstrastDetectAFArea = 0x0231,
|
||||
CrossProcessParams = 0x0235,
|
||||
LensInfoQ = 0x0239,
|
||||
Model = 0x023f,
|
||||
PixelShiftInfo = 0x0243,
|
||||
AFPointInfo = 0x0245,
|
||||
DataDump = 0x03fe,
|
||||
TempInfo = 0x03ff,
|
||||
ToneCurve = 0x0402,
|
||||
ToneCurves = 0x0403,
|
||||
UnknownBlock = 0x0405,
|
||||
PrintIM = 0x0e00,
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
+749
@@ -0,0 +1,749 @@
|
||||
use byteorder::BigEndian;
|
||||
use byteorder::LittleEndian;
|
||||
use byteorder::ReadBytesExt;
|
||||
use image::DynamicImage;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Cursor;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::CFA;
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image;
|
||||
use crate::alloc_image_plain;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::BEu32;
|
||||
use crate::bits::Endian;
|
||||
use crate::decoders::raf::fuji_decompressor::decompress_fuji;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::jfif::Jfif;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::*;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::packed::*;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::rawimage::BlackLevel;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawimage::WhiteLevel;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::DngTag;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
|
||||
mod dbp;
|
||||
mod fuji_decompressor;
|
||||
|
||||
/// RAF decoder
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct RafDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
ifd: IFD,
|
||||
makernotes: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
/// Check if file has RAF signature
|
||||
pub fn is_raf(file: &RawSource) -> bool {
|
||||
match file.subview(0, 8) {
|
||||
Ok(buf) => buf[0..8] == b"FUJIFILM"[..],
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// We need to inject a virtual IFD into main IFD.
|
||||
/// The RAF data block is not a regular TIFF structure but
|
||||
/// a proprietary structure. This tag id should be unlikely
|
||||
/// to appear in main IFD.
|
||||
const RAF_TAG_VIRTUAL_RAF_DATA: u16 = 0xfaaa;
|
||||
|
||||
/// Parse a proprietary RAF data structure and return a virtual IFD.
|
||||
/// Unfortunately, Fujifilm forgot to add a type field to these
|
||||
/// tags, so we need to match by tag.
|
||||
pub fn parse_raf_format(file: &RawSource, offset: u32) -> Result<IFD> {
|
||||
let mut entries = BTreeMap::new();
|
||||
let stream = &mut file.reader();
|
||||
stream.seek(SeekFrom::Start(offset as u64))?;
|
||||
let num = stream.read_u32::<BigEndian>()?; // Directory entries in this IFD
|
||||
if num > 4000 {
|
||||
return Err(format_args!("too many entries in IFD ({})", num).into());
|
||||
}
|
||||
for _ in 0..num {
|
||||
let tag = stream.read_u16::<BigEndian>()?;
|
||||
let len = stream.read_u16::<BigEndian>()? as usize;
|
||||
//eprintln!("RAF tag: 0x{:X}, len: {}", tag, len);
|
||||
|
||||
match RafTags::try_from(tag) {
|
||||
Ok(RafTags::RawImageFullSize)
|
||||
| Ok(RafTags::RawImageCropTopLeft)
|
||||
| Ok(RafTags::RawImageCroppedSize)
|
||||
| Ok(RafTags::RawImageAspectRatio)
|
||||
| Ok(RafTags::WB_GRGBLevels) => {
|
||||
let n = len / size_of::<u16>();
|
||||
let entry = Entry {
|
||||
tag,
|
||||
value: Value::Short((0..n).map(|_| stream.read_u16::<BigEndian>()).collect::<std::io::Result<Vec<_>>>()?),
|
||||
embedded: None,
|
||||
};
|
||||
entries.insert(tag, entry);
|
||||
}
|
||||
Ok(RafTags::FujiLayout) | Ok(RafTags::XTransLayout) => {
|
||||
let n = len / size_of::<u8>();
|
||||
let entry = Entry {
|
||||
tag,
|
||||
value: Value::Byte((0..n).map(|_| stream.read_u8()).collect::<std::io::Result<Vec<_>>>()?),
|
||||
embedded: None,
|
||||
};
|
||||
entries.insert(tag, entry);
|
||||
}
|
||||
// This one is in other byte-order...
|
||||
Ok(RafTags::RAFData) => {
|
||||
let n = len / size_of::<u32>();
|
||||
let entry = Entry {
|
||||
tag,
|
||||
value: Value::Long((0..n).map(|_| stream.read_u32::<LittleEndian>()).collect::<std::io::Result<Vec<_>>>()?),
|
||||
embedded: None,
|
||||
};
|
||||
entries.insert(tag, entry);
|
||||
}
|
||||
// Skip other tags
|
||||
_ => {
|
||||
stream.seek(SeekFrom::Current(len as i64))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(IFD {
|
||||
entries,
|
||||
endian: Endian::Big,
|
||||
offset: 0,
|
||||
base: offset as u32,
|
||||
corr: 0,
|
||||
next_ifd: 0,
|
||||
sub: Default::default(),
|
||||
chain: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// RAF format contains multiple TIFF and TIFF-like structures.
|
||||
/// This creates a IFD with all other IFDs found collected as SubIFDs.
|
||||
fn parse_raf(file: &RawSource) -> Result<IFD> {
|
||||
const RAF_TIFF1_PTR_OFFSET: u64 = 84;
|
||||
const RAF_TIFF2_PTR_OFFSET: u64 = 100;
|
||||
const RAF_TAGS_PTR_OFFSET: u64 = 92;
|
||||
//const RAF_BLOCK_PTR_OFFSET2: u64 = 120; TODO: ?!?
|
||||
log::debug!("parse RAF");
|
||||
let stream = &mut file.reader();
|
||||
stream.seek(SeekFrom::Start(RAF_TIFF1_PTR_OFFSET))?;
|
||||
let offset = stream.read_u32::<BigEndian>()?;
|
||||
|
||||
// Main IFD
|
||||
let mut main = IFD::new_root(stream, offset + 12)?;
|
||||
|
||||
//main.dump::<TiffCommonTag>(10).iter().for_each(|line| eprintln!("MAIN: {}", line));
|
||||
|
||||
// There is a second TIFF structure, the pointer is stored at offset 100.
|
||||
// If it is not a valid TIFF structure, the pointer itself is the RAF offset.
|
||||
stream.seek(SeekFrom::Start(RAF_TIFF2_PTR_OFFSET))?;
|
||||
let ioffset = stream.read_u32::<BigEndian>()?;
|
||||
|
||||
match IFD::new_root_with_correction(stream, 0, ioffset, 0, 10, &[FujiIFD::FujiIFD.into()]) {
|
||||
Ok(val) => {
|
||||
log::debug!("Found valid FujiIFD (0xF000)");
|
||||
//val.dump::<FujiIFD>(10).iter().for_each(|line| eprintln!("FujiIFD: {}", line));
|
||||
main.sub.insert(FujiIFD::FujiIFD as u16, vec![val]);
|
||||
}
|
||||
Err(_) => {
|
||||
// We fake an FujiIFD to pass the StripOffsets
|
||||
log::debug!("Unable to find FujiIFD (0xF000), let's fake it");
|
||||
let mut entries = BTreeMap::<u16, Entry>::new();
|
||||
entries.insert(
|
||||
FujiIFD::StripOffsets as u16,
|
||||
Entry {
|
||||
tag: FujiIFD::StripOffsets as u16,
|
||||
value: Value::Long(vec![ioffset]), // The ioffset is absolute to the file start.
|
||||
embedded: Some(RAF_TIFF2_PTR_OFFSET as u32),
|
||||
},
|
||||
);
|
||||
let fake = IFD {
|
||||
offset: 0,
|
||||
base: 0, // For the faked IFD, the offsets are already absolute to the file start.
|
||||
corr: 0,
|
||||
next_ifd: 0,
|
||||
entries,
|
||||
endian: main.endian,
|
||||
sub: Default::default(),
|
||||
chain: Default::default(),
|
||||
};
|
||||
main.sub.insert(FujiIFD::FujiIFD as u16, vec![fake]);
|
||||
}
|
||||
}
|
||||
// And we maybe have a RAF data block, try to parse it.
|
||||
stream.seek(SeekFrom::Start(RAF_TAGS_PTR_OFFSET))?;
|
||||
let raf_offset = stream.read_u32::<BigEndian>()?;
|
||||
match parse_raf_format(file, raf_offset) {
|
||||
Ok(val) => {
|
||||
//val.dump::<RafTags>(10).iter().for_each(|line| eprintln!("RAFTAGS: {}", line));
|
||||
main.sub.insert(RAF_TAG_VIRTUAL_RAF_DATA, vec![val]);
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("RAF block pointer is not valid, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(main)
|
||||
}
|
||||
|
||||
impl<'a> RafDecoder<'a> {
|
||||
pub fn new(file: &RawSource, rawloader: &'a RawLoader) -> Result<RafDecoder<'a>> {
|
||||
let ifd = parse_raf(file)?;
|
||||
let camera = rawloader.check_supported(&ifd)?;
|
||||
let makernotes = if let Some(exif) = ifd.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::Absolute, &[])?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
//makernotes.dump::<RafMakernotes>(10).iter().for_each(|line| eprintln!("MKND: {}", line));
|
||||
Ok(RafDecoder {
|
||||
ifd,
|
||||
makernotes,
|
||||
rawloader,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for RafDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self.ifd.find_first_ifd_with_tag(FujiIFD::StripOffsets).ok_or("No StripOffsets found")?;
|
||||
let (width, height) = if raw.has_entry(FujiIFD::RawImageFullWidth) {
|
||||
(
|
||||
fetch_tiff_tag!(raw, FujiIFD::RawImageFullWidth).force_usize(0),
|
||||
fetch_tiff_tag!(raw, FujiIFD::RawImageFullHeight).force_usize(0),
|
||||
)
|
||||
} else {
|
||||
let raf = &self
|
||||
.ifd
|
||||
.sub_ifds()
|
||||
.get(&RAF_TAG_VIRTUAL_RAF_DATA)
|
||||
.and_then(|ifds| ifds.get(0))
|
||||
.ok_or("No RAF data IFD found")?;
|
||||
let sizes = fetch_tiff_tag!(raf, TiffCommonTag::ImageWidth);
|
||||
(sizes.force_usize(1), sizes.force_usize(0))
|
||||
};
|
||||
|
||||
let bps = match raw.get_entry(TiffCommonTag::RafBitsPerSample) {
|
||||
Some(val) => val.force_u32(0) as usize,
|
||||
None => 16,
|
||||
};
|
||||
|
||||
// Rotation is only used for SuperCCD sensors, so we handle X-Trans CFA only here.
|
||||
// Some cameras like X-T20 uses different CFA when compression is enabled, so we
|
||||
// read the correct pattern from metadata.
|
||||
let corrected_cfa = if let Some(cfa) = self.get_xtrans_cfa()? {
|
||||
log::debug!(
|
||||
"Found X-Trans CFA pattern in metadata, use this instead of camera config file. Pattern is: {}",
|
||||
cfa
|
||||
);
|
||||
cfa
|
||||
} else {
|
||||
self.camera.cfa.clone()
|
||||
};
|
||||
|
||||
// Strip offset is relative to IFD base
|
||||
let offset = raw.base as u64 + fetch_tiff_tag!(raw, FujiIFD::StripOffsets).force_u64(0);
|
||||
let src = if raw.has_entry(FujiIFD::StripByteCounts) {
|
||||
let strip_count = fetch_tiff_tag!(raw, FujiIFD::StripByteCounts).force_u64(0);
|
||||
file.subview_padded(offset, strip_count)?
|
||||
} else {
|
||||
// Some models like DBP don't have a byte count, so we read until EOF
|
||||
file.subview_until_eof_padded(offset)?
|
||||
};
|
||||
|
||||
log::debug!("BPS: {}, width: {}, height: {}, offset: {}", bps, width, height, offset);
|
||||
|
||||
let image = if self.camera.find_hint("double_width") {
|
||||
// Some fuji SuperCCD cameras include a second raw image next to the first one
|
||||
// that is identical but darker to the first. The two combined can produce
|
||||
// a higher dynamic range image. Right now we're ignoring it.
|
||||
decode_16le_skiplines(&src, width, height, dummy)
|
||||
} else if self.camera.find_hint("jpeg32") {
|
||||
match bps {
|
||||
12 => decode_12be_msb32(&src, width, height, dummy),
|
||||
14 => decode_14be_msb32(&src, width, height, dummy),
|
||||
_ => return Err(RawlerError::unsupported(&self.camera, format!("RAF: Don't know how to decode bps {}", bps))),
|
||||
}
|
||||
} else if self.camera.clean_model == "DBP for GX680" {
|
||||
assert_eq!(bps, 16);
|
||||
dbp::decode_dbp(&src, width, height, dummy)?
|
||||
} else if src.len() < bps * width * height / 8 {
|
||||
if !dummy {
|
||||
decompress_fuji(&src, width, height, bps, &corrected_cfa)?
|
||||
} else {
|
||||
alloc_image_plain!(width, height, dummy)
|
||||
}
|
||||
} else {
|
||||
match bps {
|
||||
12 => decode_12le(&src, width, height, dummy),
|
||||
14 => decode_14le_unpacked(&src, width, height, dummy),
|
||||
16 => {
|
||||
if self.ifd.endian == Endian::Little {
|
||||
decode_16le(&src, width, height, dummy)
|
||||
} else {
|
||||
decode_16be(&src, width, height, dummy)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(RawlerError::unsupported(&self.camera, format!("RAF: Don't know how to decode bps {}", bps)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let blacklevel = self.get_blacklevel(&corrected_cfa)?;
|
||||
log::debug!("RAF Blacklevels: {:?}", blacklevel);
|
||||
|
||||
// For now, we put the rotated data into DNG. Much better solution
|
||||
// would be to support staggered layouts, but this is not used much
|
||||
// and complicated to implement, because we need rectangular CFA patterns like 2x4.
|
||||
// The code path for staggered data is already implemented here, but remains unused.
|
||||
let rotate_for_dng = false;
|
||||
let cpp = 1;
|
||||
if self.camera.find_hint("fuji_rotation") || self.camera.find_hint("fuji_rotation_alt") {
|
||||
log::debug!("Apply Fuji image rotation");
|
||||
let rotated = if rotate_for_dng {
|
||||
if self.camera.find_hint("fuji_rotation") {
|
||||
fuji_raw_rotate(&image, dummy) // Only required for fuji_rotation
|
||||
} else {
|
||||
image
|
||||
}
|
||||
} else {
|
||||
self.rotate_image(image.pixels(), &self.camera, width, height, dummy)?
|
||||
};
|
||||
|
||||
let mut camera = self.camera.clone();
|
||||
camera.cfa = corrected_cfa;
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&camera));
|
||||
let mut image = RawImage::new(
|
||||
self.camera.clone(),
|
||||
rotated,
|
||||
cpp,
|
||||
normalize_wb(self.get_wb()?),
|
||||
photometric,
|
||||
blacklevel,
|
||||
None,
|
||||
dummy,
|
||||
);
|
||||
|
||||
if rotate_for_dng {
|
||||
image.add_dng_tag(TiffCommonTag::CFARepeatPatternDim, [2, 4]);
|
||||
image.add_dng_tag(DngTag::CFALayout, 2_u16);
|
||||
image.add_dng_tag(TiffCommonTag::CFAPattern, &[0_u8, 1, 2, 1, 2, 1, 0, 1][..]);
|
||||
|
||||
todo!();
|
||||
//image.add_dng_tag(DngTag::BlackLevel, image.blacklevel[0]);
|
||||
//image.add_dng_tag(DngTag::BlackLevelRepeatDim, [1_u16, 1_u16]);
|
||||
}
|
||||
|
||||
// Reset crops because we have rotated the data.
|
||||
image.active_area = None;
|
||||
image.crop_area = None;
|
||||
Ok(image)
|
||||
} else {
|
||||
//ok_image(self.camera.clone(), width, height, cpp, self.get_wb()?, image.into_inner())
|
||||
|
||||
let mut camera = self.camera.clone();
|
||||
camera.cfa = corrected_cfa;
|
||||
let whitelevel = if self.camera.whitelevel.is_none() {
|
||||
match bps {
|
||||
12 | 14 | 16 => {
|
||||
let max_value: u32 = (1_u32 << bps) - 1;
|
||||
Some(WhiteLevel::new(vec![max_value; cpp]))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&camera));
|
||||
let mut image = RawImage::new(camera, image, cpp, normalize_wb(self.get_wb()?), photometric, blacklevel, whitelevel, dummy);
|
||||
|
||||
// Overwrite crop if available in metadata
|
||||
if let Some(crop) = self.get_crop()? {
|
||||
log::debug!("RAW file metadata contains crop info, overriding toml definitions: {:?}", crop);
|
||||
image.crop_area = Some(crop);
|
||||
}
|
||||
|
||||
// Ideally, someone would expect that area is at bayer pattern
|
||||
// boundary. This is not the case, so we don't check this here.
|
||||
// if let Some(_area) = image.active_area.as_ref() {
|
||||
// assert_eq!(area.d.w % image.cfa.width , 0);
|
||||
// assert_eq!(area.d.h % image.cfa.height , 0);
|
||||
// }
|
||||
Ok(image)
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let mut exif = Exif::new(&self.ifd)?;
|
||||
// Fuji RAF has all EXIF tags we need and there is no LensID or something
|
||||
// we can lookup. So this is an exception, we just pass the information.
|
||||
// TODO: better imeplement LensData::from_exif()?
|
||||
if let Some(ifd) = self.ifd.get_sub_ifd(TiffCommonTag::ExifIFDPointer) {
|
||||
exif.lens_make = ifd.get_entry(ExifTag::LensMake).and_then(|entry| entry.as_string().cloned());
|
||||
exif.lens_model = ifd.get_entry(ExifTag::LensModel).and_then(|entry| entry.as_string().cloned());
|
||||
exif.lens_spec = ifd.get_entry(ExifTag::LensSpecification).and_then(|entry| match &entry.value {
|
||||
Value::Rational(data) => Some([data[0], data[1], data[2], data[3]]),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
let mdata = RawMetadata::new(&self.camera, exif);
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn xpacket(&self, file: &RawSource, _params: &RawDecodeParams) -> Result<Option<Vec<u8>>> {
|
||||
let jpeg_buf = self.read_embedded_jpeg(file)?;
|
||||
let mut cur = Cursor::new(jpeg_buf);
|
||||
let jfif = Jfif::parse(&mut cur)?;
|
||||
match jfif.xpacket().cloned() {
|
||||
Some(xpacket) => {
|
||||
log::debug!("Found XPacket data in embedded JPEG preview");
|
||||
Ok(Some(xpacket))
|
||||
}
|
||||
None => {
|
||||
log::debug!("Found no XPacket data");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let jpeg_buf = self.read_embedded_jpeg(file)?;
|
||||
let img = image::load_from_memory_with_format(jpeg_buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to read JPEG: {:?}", err)))?;
|
||||
Ok(Some(img))
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::RAF
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RafDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let raw = self.ifd.find_first_ifd_with_tag(FujiIFD::StripOffsets).ok_or("No StripOffsets found")?;
|
||||
match raw.get_entry(FujiIFD::WB_GRBLevels) {
|
||||
Some(levels) => Ok([levels.force_f32(1), levels.force_f32(0), levels.force_f32(0), levels.force_f32(2)]),
|
||||
None => {
|
||||
let raf = &self
|
||||
.ifd
|
||||
.sub_ifds()
|
||||
.get(&RAF_TAG_VIRTUAL_RAF_DATA)
|
||||
.and_then(|ifds| ifds.get(0))
|
||||
.ok_or("No RAF data IFD found")?;
|
||||
let levels = fetch_tiff_tag!(raf, TiffCommonTag::RafOldWB);
|
||||
Ok([levels.force_f32(1), levels.force_f32(0), levels.force_f32(0), levels.force_f32(3)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_blacklevel(&self, cfa: &CFA) -> Result<Option<BlackLevel>> {
|
||||
if let Some(fuji) = self.ifd.get_sub_ifd(FujiIFD::FujiIFD) {
|
||||
if let Some(Entry { value: Value::Long(black), .. }) = fuji.get_entry_recursive(FujiIFD::BlackLevel) {
|
||||
let levels: Vec<u16> = black.iter().copied().map(|v| v as u16).collect();
|
||||
return Ok(Some(BlackLevel::new(&levels, cfa.width, cfa.height, 1)));
|
||||
} else {
|
||||
log::debug!("Unable to find black level data");
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get crop from metadata
|
||||
/// Nearly all models have this parameter, except of FinePix HS10
|
||||
fn get_crop(&self) -> Result<Option<Rect>> {
|
||||
if let Some(raf) = &self.ifd.sub_ifds().get(&RAF_TAG_VIRTUAL_RAF_DATA).and_then(|ifds| ifds.get(0)) {
|
||||
let crops = raf.get_entry(RafTags::RawImageCropTopLeft);
|
||||
let size = raf.get_entry(RafTags::RawImageCroppedSize);
|
||||
if let (Some(crops), Some(size)) = (crops, size) {
|
||||
return Ok(Some(Rect::new(
|
||||
Point::new(crops.force_usize(1), crops.force_usize(0)),
|
||||
Dim2::new(size.force_usize(1), size.force_usize(0)),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get the X-Trans CFA pattern
|
||||
/// This is encoded in RAF metadata block in XTransLayout.
|
||||
/// For unknown reason, the values are stored in reverse order and
|
||||
/// also falsely reported by exiftoool.
|
||||
fn get_xtrans_cfa(&self) -> Result<Option<CFA>> {
|
||||
Ok(
|
||||
if let Some(raf) = &self
|
||||
.ifd
|
||||
.sub_ifds()
|
||||
.get(&RAF_TAG_VIRTUAL_RAF_DATA)
|
||||
.and_then(|ifds| ifds.get(0).and_then(|ifd| ifd.get_entry(RafTags::XTransLayout)))
|
||||
{
|
||||
match &raf.value {
|
||||
Value::Byte(data) => {
|
||||
let patname: String = data
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|v| match v {
|
||||
0 => 'R',
|
||||
1 => 'G',
|
||||
2 => 'B',
|
||||
_ => 'X', // Unknown, let CFA::new() fail...
|
||||
})
|
||||
.collect();
|
||||
Some(CFA::new(&patname))
|
||||
}
|
||||
_ => {
|
||||
return Err("Invalid XTransLayout data type".into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn read_embedded_jpeg<'b>(&self, file: &'b RawSource) -> Result<&'b [u8]> {
|
||||
// The offset and len of JPEG preview is in the RAF structure
|
||||
let buf = file.subview(0, 84 + 8)?;
|
||||
let jpeg_off = BEu32(buf, 84) as u64;
|
||||
let jpeg_len = BEu32(buf, 84 + 4) as u64;
|
||||
log::debug!("JPEG off: {}, len: {}", jpeg_off, jpeg_len);
|
||||
Ok(file.subview(jpeg_off, jpeg_len)?)
|
||||
}
|
||||
|
||||
fn rotate_image(&self, src: &[u16], camera: &Camera, width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
if let Some(active_area) = self.camera.active_area {
|
||||
let x = active_area[0];
|
||||
let y = active_area[1];
|
||||
let cropwidth = width - active_area[2] - x;
|
||||
let cropheight = height - active_area[3] - y; // TODO: bug, invalid order of crop index
|
||||
|
||||
if camera.find_hint("fuji_rotation_alt") {
|
||||
let rotatedwidth = cropheight + cropwidth / 2;
|
||||
let rotatedheight = rotatedwidth - 1;
|
||||
|
||||
let mut out = alloc_image_plain!(rotatedwidth, rotatedheight, dummy);
|
||||
if !dummy {
|
||||
for row in 0..cropheight {
|
||||
let inb = &src[(row + y) * width + x..];
|
||||
for col in 0..cropwidth {
|
||||
let out_row = rotatedwidth - (cropheight + 1 - row + (col >> 1));
|
||||
let out_col = ((col + 1) >> 1) + row;
|
||||
out[out_row * rotatedwidth + out_col] = inb[col];
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
} else {
|
||||
let rotatedwidth = cropwidth + cropheight / 2;
|
||||
let rotatedheight = rotatedwidth - 1;
|
||||
|
||||
let mut out = alloc_image_plain!(rotatedwidth, rotatedheight, dummy);
|
||||
if !dummy {
|
||||
for row in 0..cropheight {
|
||||
let inb = &src[(row + y) * width + x..];
|
||||
for col in 0..cropwidth {
|
||||
let out_row = cropwidth - 1 - col + (row >> 1);
|
||||
let out_col = ((row + 1) >> 1) + col;
|
||||
out[out_row * rotatedwidth + out_col] = inb[col];
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
} else {
|
||||
Err(RawlerError::DecoderFailed("no active_area for fuji_rotate".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
log::debug!("RAF raw wb: {:?}", raw_wb);
|
||||
// We never have more then RGB colors so far (no RGBE etc.)
|
||||
// So we combine G1 and G2 to get RGB wb.
|
||||
let div = raw_wb[1];
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(RafMakernotes);
|
||||
crate::tags::tiff_tag_enum!(FujiIFD);
|
||||
crate::tags::tiff_tag_enum!(RafTags);
|
||||
|
||||
/// Specific RAF Makernotes tags.
|
||||
/// These are only related to the Makernote IFD.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum RafMakernotes {
|
||||
Version = 0x0000,
|
||||
InternalSerialNumber = 0x0010,
|
||||
Quality = 0x1000,
|
||||
Sharpness = 0x1001,
|
||||
WhiteBalance = 0x1002,
|
||||
Saturation = 0x1003,
|
||||
Contrast = 0x1004,
|
||||
ColorTemperature = 0x1005,
|
||||
Contrast2 = 0x1006,
|
||||
WhiteBalanceFineTune = 0x100a,
|
||||
NoiseReduction = 0x100b,
|
||||
NoiseReduction2 = 0x100e,
|
||||
FujiFlashMode = 0x1010,
|
||||
FlashExposureComp = 0x1011,
|
||||
Macro = 0x1020,
|
||||
FocusMode = 0x1021,
|
||||
AFMode = 0x1022,
|
||||
FocusPixel = 0x1023,
|
||||
PrioritySettings = 0x102b,
|
||||
FocusSettings = 0x102d,
|
||||
AFCSettings = 0x102e,
|
||||
SlowSync = 0x1030,
|
||||
PictureMode = 0x1031,
|
||||
ExposureCount = 0x1032,
|
||||
EXRAuto = 0x1033,
|
||||
EXRMode = 0x1034,
|
||||
ShadowTone = 0x1040,
|
||||
HighlightTone = 0x1041,
|
||||
DigitalZoom = 0x1044,
|
||||
LensModulationOptimizer = 0x1045,
|
||||
GrainEffect = 0x1047,
|
||||
ColorChromeEffect = 0x1048,
|
||||
BWAdjustment = 0x1049,
|
||||
CropMode = 0x104d,
|
||||
ColorChromeFXBlue = 0x104e,
|
||||
ShutterType = 0x1050,
|
||||
AutoBracketing = 0x1100,
|
||||
SequenceNumber = 0x1101,
|
||||
DriveSettings = 0x1103,
|
||||
PixelShiftShots = 0x1105,
|
||||
PixelShiftOffset = 0x1106,
|
||||
PanoramaAngle = 0x1153,
|
||||
PanoramaDirection = 0x1154,
|
||||
AdvancedFilter = 0x1201,
|
||||
ColorMode = 0x1210,
|
||||
BlurWarning = 0x1300,
|
||||
FocusWarning = 0x1301,
|
||||
ExposureWarning = 0x1302,
|
||||
GEImageSize = 0x1304,
|
||||
DynamicRange = 0x1400,
|
||||
FilmMode = 0x1401,
|
||||
DynamicRangeSetting = 0x1402,
|
||||
DevelopmentDynamicRange = 0x1403,
|
||||
MinFocalLength = 0x1404,
|
||||
MaxFocalLength = 0x1405,
|
||||
MaxApertureAtMinFocal = 0x1406,
|
||||
MaxApertureAtMaxFocal = 0x1407,
|
||||
AutoDynamicRange = 0x140b,
|
||||
ImageStabilization = 0x1422,
|
||||
SceneRecognition = 0x1425,
|
||||
Rating = 0x1431,
|
||||
ImageGeneration = 0x1436,
|
||||
ImageCount = 0x1438,
|
||||
DRangePriority = 0x1443,
|
||||
DRangePriorityAuto = 0x1444,
|
||||
DRangePriorityFixed = 0x1445,
|
||||
FlickerReduction = 0x1446,
|
||||
VideoRecordingMode = 0x3803,
|
||||
PeripheralLighting = 0x3804,
|
||||
VideoCompression = 0x3806,
|
||||
FrameRate = 0x3820,
|
||||
FrameWidth = 0x3821,
|
||||
FrameHeight = 0x3822,
|
||||
FullHDHighSpeedRec = 0x3824,
|
||||
FaceElementSelected = 0x4005,
|
||||
FacesDetected = 0x4100,
|
||||
FacePositions = 0x4103,
|
||||
NumFaceElements = 0x4200,
|
||||
FaceElementTypes = 0x4201,
|
||||
FaceElementPositions = 0x4203,
|
||||
FaceRecInfo = 0x4282,
|
||||
FileSource = 0x8000,
|
||||
OrderNumber = 0x8002,
|
||||
FrameNumber = 0x8003,
|
||||
Parallax = 0xb211,
|
||||
}
|
||||
|
||||
/// These are only related to the additional FujiIFD in RAF files
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum FujiIFD {
|
||||
FujiIFD = 0xf000,
|
||||
RawImageFullWidth = 0xf001,
|
||||
RawImageFullHeight = 0xf002,
|
||||
BitsPerSample = 0xf003,
|
||||
StripOffsets = 0xf007,
|
||||
StripByteCounts = 0xf008,
|
||||
BlackLevel = 0xf00a,
|
||||
GeometricDistortionParams = 0xf00b,
|
||||
WB_GRBLevelsStandard = 0xf00c,
|
||||
WB_GRBLevelsAuto = 0xf00d,
|
||||
WB_GRBLevels = 0xf00e,
|
||||
ChromaticAberrationParams = 0xf00f,
|
||||
VignettingParams = 0xf010,
|
||||
}
|
||||
|
||||
/// These are only related to the additional RAF-tags in RAF files
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum RafTags {
|
||||
RawImageFullSize = 0x0100,
|
||||
RawImageCropTopLeft = 0x0110,
|
||||
RawImageCroppedSize = 0x0111,
|
||||
RawImageAspectRatio = 0x0115,
|
||||
RawImageSize = 0x0121,
|
||||
FujiLayout = 0x0130,
|
||||
XTransLayout = 0x0131,
|
||||
WB_GRGBLevels = 0x2ff0,
|
||||
RelativeExposure = 0x9200,
|
||||
RawExposureBias = 0x9650,
|
||||
RAFData = 0xc000,
|
||||
}
|
||||
|
||||
pub fn fuji_raw_rotate(img: &PixU16, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(img.height, img.width, dummy);
|
||||
for row in 0..img.height {
|
||||
for col in 0..img.width {
|
||||
//*x.at_mut(row, col) = out[flip_index(row, col)];
|
||||
//*x.at_mut(row, col) = out[(width - 1 - col) * height + (height - 1 - row)];
|
||||
//*out.at_mut(img.width - 1 - col, img.height - 1 - row) = *img.at(row, col); // out[row * width + col];
|
||||
*out.at_mut(col, row) = *img.at(row, col); // out[row * width + col];
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
///
|
||||
/// Original code by libraw and rawspeed, licensed under LGPL-2
|
||||
///
|
||||
/// Copyright (C) 2016 Alexey Danilchenko
|
||||
/// Copyright (C) 2016 Alex Tutubalin
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::Result;
|
||||
use crate::{alloc_image_ok, pixarray::PixU16};
|
||||
|
||||
pub(super) fn decode_dbp(buf: &[u8], width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
let mut cursor = Cursor::new(buf);
|
||||
let n_tiles = 8;
|
||||
let tile_width = width / n_tiles;
|
||||
let _tile_height = 3856;
|
||||
log::debug!("DBP width: {}, height: {}, tile: {}", width, height, tile_width);
|
||||
|
||||
let mut tile_buffer = vec![0_u16; height * tile_width];
|
||||
|
||||
for tile_n in 0..n_tiles {
|
||||
cursor.read_u16_into::<BigEndian>(&mut tile_buffer)?;
|
||||
for scan_line in 0..height {
|
||||
let off = scan_line * width + tile_n * tile_width;
|
||||
out[off..off + tile_width].copy_from_slice(&tile_buffer[scan_line * tile_width..scan_line * tile_width + tile_width]);
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+447
@@ -0,0 +1,447 @@
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::CFA;
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::Entry;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::Rational;
|
||||
use crate::formats::tiff::Value;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::imgop::Dim2;
|
||||
use crate::imgop::Point;
|
||||
use crate::imgop::Rect;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::packed::decode_12le_unpacked_left_aligned;
|
||||
use crate::packed::decode_12le_wcontrol;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::rawimage::CFAConfig;
|
||||
use crate::rawimage::RawPhotometricInterpretation;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
use crate::tags::tiff_tag_enum;
|
||||
|
||||
use self::v4decompressor::decode_panasonic_v4;
|
||||
use self::v5decompressor::decode_panasonic_v5;
|
||||
use self::v6decompressor::decode_panasonic_v6;
|
||||
use self::v7decompressor::decode_panasonic_v7;
|
||||
use self::v8decompressor::decode_panasonic_v8;
|
||||
|
||||
use super::BlackLevel;
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
|
||||
pub(crate) mod v4decompressor;
|
||||
pub(crate) mod v5decompressor;
|
||||
pub(crate) mod v6decompressor;
|
||||
pub(crate) mod v7decompressor;
|
||||
pub(crate) mod v8decompressor;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rw2Decoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
camera_ifd: Option<IFD>,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> Rw2Decoder<'a> {
|
||||
pub fn new(_file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<Rw2Decoder<'a>> {
|
||||
let raw = {
|
||||
let data = tiff.find_ifds_with_tag(TiffCommonTag::PanaOffsets);
|
||||
if !data.is_empty() {
|
||||
data[0]
|
||||
} else {
|
||||
tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with StripOffsets tag")))?
|
||||
}
|
||||
};
|
||||
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::PanaWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::PanaLength).force_usize(0);
|
||||
|
||||
let mode = {
|
||||
let ratio = width * 100 / height;
|
||||
if ratio < 125 {
|
||||
"1:1"
|
||||
} else if ratio < 145 {
|
||||
"4:3"
|
||||
} else if ratio < 165 {
|
||||
"3:2"
|
||||
} else {
|
||||
"16:9"
|
||||
}
|
||||
};
|
||||
let camera = rawloader.check_supported_with_mode(tiff.root_ifd(), mode)?;
|
||||
|
||||
let camera_ifd = if let Some(ifd) = tiff.get_entry(PanasonicTag::CameraIFD) {
|
||||
let buf = ifd.get_data();
|
||||
match IFD::new_root(&mut std::io::Cursor::new(buf), 0) {
|
||||
Ok(ifd) => Some(ifd),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Rw2Decoder {
|
||||
rawloader,
|
||||
tiff,
|
||||
camera_ifd,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for Rw2Decoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let width;
|
||||
let height;
|
||||
|
||||
let (raw, split) = {
|
||||
let data = self.tiff.find_ifds_with_tag(TiffCommonTag::PanaOffsets);
|
||||
if !data.is_empty() {
|
||||
(data[0], true)
|
||||
} else {
|
||||
(
|
||||
self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with StripOffsets tag")))?,
|
||||
false,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let compression = raw.get_entry(PanasonicTag::Compression).map(|entry| entry.force_u16(0)).unwrap_or_default(); // TODO BUG
|
||||
//let compression = fetch_tiff_tag!(raw, PanasonicTag::Compression).force_u16(0);
|
||||
|
||||
let raw_format = raw.get_entry(PanasonicTag::RawFormat).map(|entry| entry.force_u16(0)).unwrap_or_default(); // TODO BUG
|
||||
|
||||
let bps = fetch_tiff_tag!(raw, PanasonicTag::BitsPerSample).force_u32(0);
|
||||
let multishot = raw.get_entry(PanasonicTag::Multishot).map(|entry| entry.force_u32(0) == 65536).unwrap_or(false);
|
||||
|
||||
let image = {
|
||||
let data = self.tiff.find_ifds_with_tag(TiffCommonTag::PanaOffsets);
|
||||
if !data.is_empty() {
|
||||
let raw = data[0];
|
||||
width = fetch_tiff_tag!(raw, TiffCommonTag::PanaWidth).force_usize(0);
|
||||
height = fetch_tiff_tag!(raw, TiffCommonTag::PanaLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::PanaOffsets).force_usize(0);
|
||||
//let size = fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts).force_usize(0);
|
||||
log::debug!("PanaOffset: {}", offset);
|
||||
let src = file.subview_until_eof_padded(offset as u64)?; // TODO add size and check all samples
|
||||
Rw2Decoder::decode_panasonic(file, &src, width, height, split, raw_format, bps, self.tiff.root_ifd(), dummy)?
|
||||
} else {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with StripOffsets tag")))?;
|
||||
width = fetch_tiff_tag!(raw, TiffCommonTag::PanaWidth).force_usize(0);
|
||||
height = fetch_tiff_tag!(raw, TiffCommonTag::PanaLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
//let size = fetch_tiff_tag!(raw, TiffCommonTag::StripByteCounts).force_usize(0);
|
||||
log::debug!("StripOffset: {}", offset);
|
||||
let src = file.subview_until_eof_padded(offset as u64)?; // TODO add size and check all samples
|
||||
|
||||
if src.len() >= width * height * 2 {
|
||||
decode_12le_unpacked_left_aligned(&src, width, height, dummy)
|
||||
} else if src.len() >= width * height * 3 / 2 {
|
||||
decode_12le_wcontrol(&src, width, height, dummy)
|
||||
} else {
|
||||
Rw2Decoder::decode_panasonic(file, &src, width, height, split, raw_format, bps, self.tiff.root_ifd(), dummy)?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"RW2 raw: {}, compression: {}, bps: {}, width: {}, height: {}, multishot: {}",
|
||||
raw_format,
|
||||
compression,
|
||||
bps,
|
||||
width,
|
||||
height,
|
||||
multishot
|
||||
);
|
||||
|
||||
let cpp = 1;
|
||||
let blacklevel = self.get_blacklevel()?;
|
||||
let mut camera = self.camera.clone();
|
||||
if let Some(cfa) = self.get_cfa()? {
|
||||
camera.cfa = cfa;
|
||||
}
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&camera));
|
||||
let mut img = RawImage::new(camera, image, cpp, normalize_wb(self.get_wb()?), photometric, blacklevel, None, dummy);
|
||||
|
||||
if let Some(area) = self.get_active_area()? {
|
||||
img.active_area = Some(area);
|
||||
img.crop_area = Some(area);
|
||||
} else if let Some(area) = self.get_crop()? {
|
||||
img.crop_area = Some(area);
|
||||
}
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn full_image(&self, _file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(data) = self.tiff.get_entry(PanasonicTag::JpegData) {
|
||||
let buf = data.get_data();
|
||||
let img = image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|e| RawlerError::DecoderFailed(format!("Unable to load jpeg preview: {:?}", e)))?;
|
||||
return Ok(Some(img));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let mut exif = Exif::new(self.tiff.root_ifd())?;
|
||||
if exif.iso_speed.unwrap_or(0) == 0 && exif.iso_speed_ratings.unwrap_or(0) == 0 && exif.recommended_exposure_index.unwrap_or(0) == 0 {
|
||||
// Use ISO from PanasonicRaw IFD
|
||||
if let Some(iso) = self.tiff.get_entry(PanasonicTag::ISO) {
|
||||
exif.iso_speed_ratings = Some(iso.force_u16(0));
|
||||
}
|
||||
}
|
||||
let mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::RW2
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Rw2Decoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
if self.tiff.has_entry(PanasonicTag::PanaWBsR) && self.tiff.has_entry(PanasonicTag::PanaWBsB) {
|
||||
let r = fetch_tiff_tag!(self.tiff, PanasonicTag::PanaWBsR).force_u32(0) as f32;
|
||||
let b = fetch_tiff_tag!(self.tiff, PanasonicTag::PanaWBsB).force_u32(0) as f32;
|
||||
Ok([r, 256.0, 256.0, b])
|
||||
} else if self.tiff.has_entry(PanasonicTag::PanaWBs2R) && self.tiff.has_entry(PanasonicTag::PanaWBs2G) && self.tiff.has_entry(PanasonicTag::PanaWBs2B) {
|
||||
let r = fetch_tiff_tag!(self.tiff, PanasonicTag::PanaWBs2R).force_u32(0) as f32;
|
||||
let g = fetch_tiff_tag!(self.tiff, PanasonicTag::PanaWBs2G).force_u32(0) as f32;
|
||||
let b = fetch_tiff_tag!(self.tiff, PanasonicTag::PanaWBs2B).force_u32(0) as f32;
|
||||
Ok([r, g, g, b])
|
||||
} else {
|
||||
Err(RawlerError::DecoderFailed("RW2: Couldn't find WB".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cfa(&self) -> Result<Option<CFA>> {
|
||||
if self.tiff.has_entry(PanasonicTag::CFAPattern) {
|
||||
let pattern = fetch_tiff_tag!(self.tiff, PanasonicTag::CFAPattern).force_u16(0);
|
||||
Ok(Some(match pattern {
|
||||
1 => CFA::new("RGGB"),
|
||||
2 => CFA::new("GRBG"),
|
||||
3 => CFA::new("GBRG"),
|
||||
4 => CFA::new("BGGR"),
|
||||
_ => return Err(format!("RW2: Unknown CFA pattern: {}", pattern).into()),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_blacklevel(&self) -> Result<Option<BlackLevel>> {
|
||||
if self.tiff.has_entry(PanasonicTag::BlackLevelRed) {
|
||||
let r = fetch_tiff_tag!(self.tiff, PanasonicTag::BlackLevelRed).force_u16(0);
|
||||
let g = fetch_tiff_tag!(self.tiff, PanasonicTag::BlackLevelGreen).force_u16(0);
|
||||
let b = fetch_tiff_tag!(self.tiff, PanasonicTag::BlackLevelBlue).force_u16(0);
|
||||
Ok(Some(BlackLevel::new(&[r, g, g, b], self.camera.cfa.width, self.camera.cfa.height, 1)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
const MFT_MOUNT: &str = "MFT-mount";
|
||||
if let Some(ifd) = &self.camera_ifd {
|
||||
if ifd.has_entry(CameraIfdTag::LensTypeMake) && ifd.has_entry(CameraIfdTag::LensTypeModel) {
|
||||
let make_id = fetch_tiff_tag!(ifd, CameraIfdTag::LensTypeMake);
|
||||
let model_id = fetch_tiff_tag!(ifd, CameraIfdTag::LensTypeModel);
|
||||
|
||||
if make_id.value_type() == 3 && model_id.value_type() == 3 {
|
||||
let composite_id = format!(
|
||||
"{:02X} {:02X} {:02X}",
|
||||
make_id.force_u16(0) & 0xFF,
|
||||
model_id.force_u16(0) & 0xFF,
|
||||
model_id.force_u16(0) >> 8
|
||||
);
|
||||
log::debug!("RW2 lens composite ID: {}", composite_id);
|
||||
let resolver = LensResolver::new()
|
||||
.with_camera(&self.camera)
|
||||
.with_olympus_id(Some(composite_id))
|
||||
.with_focal_len(self.get_focal_len()?)
|
||||
.with_mounts(&[MFT_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
} else {
|
||||
log::info!("Unknown value types for lens tags: {}, {}", make_id.value_type(), model_id.value_type());
|
||||
}
|
||||
}
|
||||
}
|
||||
log::warn!("No lens data available");
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_focal_len(&self) -> Result<Option<Rational>> {
|
||||
if let Some(exif) = self.tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
if let Some(Entry {
|
||||
value: Value::Short(focal), ..
|
||||
}) = exif.get_entry(ExifTag::FocalLength)
|
||||
{
|
||||
return Ok(focal.get(1).map(|v| Rational::new(*v as u32, 1)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_crop(&self) -> Result<Option<Rect>> {
|
||||
if self.tiff.has_entry(PanasonicTag::CropLeft) {
|
||||
let crop_left = fetch_tiff_tag!(self.tiff, PanasonicTag::CropLeft).force_usize(0);
|
||||
let crop_top = fetch_tiff_tag!(self.tiff, PanasonicTag::CropTop).force_usize(0);
|
||||
let crop_right = fetch_tiff_tag!(self.tiff, PanasonicTag::CropRight).force_usize(0);
|
||||
let crop_bottom = fetch_tiff_tag!(self.tiff, PanasonicTag::CropBottom).force_usize(0);
|
||||
Ok(Some(Rect::new(
|
||||
Point::new(crop_left, crop_top),
|
||||
Dim2::new(crop_right - crop_left, crop_bottom - crop_top),
|
||||
)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_active_area(&self) -> Result<Option<Rect>> {
|
||||
if self.tiff.has_entry(PanasonicTag::SensorLeftBorder) {
|
||||
let sensor_left = fetch_tiff_tag!(self.tiff, PanasonicTag::SensorLeftBorder).force_usize(0);
|
||||
let sensor_top = fetch_tiff_tag!(self.tiff, PanasonicTag::SensorTopBorder).force_usize(0);
|
||||
let sensor_right = fetch_tiff_tag!(self.tiff, PanasonicTag::SensorRightBorder).force_usize(0);
|
||||
let sensor_bottom = fetch_tiff_tag!(self.tiff, PanasonicTag::SensorBottomBorder).force_usize(0);
|
||||
Ok(Some(Rect::new(
|
||||
Point::new(sensor_left, sensor_top),
|
||||
Dim2::new(sensor_right - sensor_left, sensor_bottom - sensor_top),
|
||||
)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode_panasonic(
|
||||
file: &RawSource,
|
||||
buf: &[u8],
|
||||
width: usize,
|
||||
height: usize,
|
||||
split: bool,
|
||||
raw_format: u16,
|
||||
bps: u32,
|
||||
ifd: &IFD,
|
||||
dummy: bool,
|
||||
) -> Result<PixU16> {
|
||||
log::debug!("width: {}, height: {}, bps: {}", width, height, bps);
|
||||
Ok(match raw_format {
|
||||
3 => decode_panasonic_v4(buf, width, height, split, dummy),
|
||||
4 => decode_panasonic_v4(buf, width, height, split, dummy),
|
||||
5 => decode_panasonic_v5(buf, width, height, bps, dummy),
|
||||
6 => decode_panasonic_v6(buf, width, height, bps, dummy),
|
||||
7 => decode_panasonic_v7(buf, width, height, bps, dummy),
|
||||
8 => decode_panasonic_v8(file, width, height, bps, ifd, dummy)?,
|
||||
_ => todo!("Format {} is not implemented", raw_format), // TODO: return error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_wb(raw_wb: [f32; 4]) -> [f32; 4] {
|
||||
log::debug!("RW2 raw wb: {:?}", raw_wb);
|
||||
let div = raw_wb[1];
|
||||
let mut norm = raw_wb;
|
||||
norm.iter_mut().for_each(|v| {
|
||||
if v.is_normal() {
|
||||
*v /= div
|
||||
}
|
||||
});
|
||||
[norm[0], (norm[1] + norm[2]) / 2.0, norm[3], f32::NAN]
|
||||
}
|
||||
|
||||
tiff_tag_enum!(PanasonicTag);
|
||||
tiff_tag_enum!(CameraIfdTag);
|
||||
|
||||
/// Common tags, generally used in root IFD or SubIFDs
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum PanasonicTag {
|
||||
PanaWidth = 0x0002,
|
||||
PanaLength = 0x0003,
|
||||
SensorTopBorder = 0x0004,
|
||||
SensorLeftBorder = 0x0005,
|
||||
SensorBottomBorder = 0x0006,
|
||||
SensorRightBorder = 0x0007,
|
||||
SamplesPerPixel = 0x0008,
|
||||
CFAPattern = 0x0009,
|
||||
BitsPerSample = 0x000a,
|
||||
Compression = 0x000b,
|
||||
PanaWBsR = 0x0011,
|
||||
PanaWBsB = 0x0012,
|
||||
ISO = 0x0017,
|
||||
|
||||
BlackLevelRed = 0x001c,
|
||||
BlackLevelGreen = 0x001d,
|
||||
BlackLevelBlue = 0x001e,
|
||||
|
||||
PanaWBs2R = 0x0024,
|
||||
PanaWBs2G = 0x0025,
|
||||
PanaWBs2B = 0x0026,
|
||||
RawFormat = 0x0002d,
|
||||
JpegData = 0x002e,
|
||||
CropTop = 0x002f,
|
||||
CropLeft = 0x0030,
|
||||
CropBottom = 0x0031,
|
||||
CropRight = 0x0032,
|
||||
|
||||
CF2StripHeight = 0x0037,
|
||||
CF2Unknown1 = 0x0039, // Gamma table CF2_GammaSlope?
|
||||
CF2Unknown2 = 0x003a, // Gamma table CF2_GammaPoint?
|
||||
CF2ClipVal = 0x003b, // CF2_GammaClipVal
|
||||
CF2HufInitVal0 = 0x003c,
|
||||
CF2HufInitVal1 = 0x003d,
|
||||
CF2HufInitVal2 = 0x003e,
|
||||
CF2HufInitVal3 = 0x003f,
|
||||
CF2HufTable = 0x0040,
|
||||
CF2HufShiftDown = 0x0041,
|
||||
CF2NumberOfStripsH = 0x0042,
|
||||
CF2NumberOfStripsV = 0x0043,
|
||||
CF2StripByteOffsets = 0x0044,
|
||||
CF2StripLineOffsets = 0x0045,
|
||||
CF2StripDataSize = 0x0046,
|
||||
CF2StripWidths = 0x0047,
|
||||
CF2StripHeights = 0x0048,
|
||||
CF2StripWidth = 0x0064,
|
||||
|
||||
CameraIFD = 0x0120,
|
||||
Multishot = 0x0121,
|
||||
}
|
||||
|
||||
/// Common tags, generally used in root IFD or SubIFDs
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum CameraIfdTag {
|
||||
LensTypeMake = 0x1201,
|
||||
LensTypeModel = 0x1202,
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::{bits::LEu16, decoders::*, pumps::BitPump};
|
||||
|
||||
pub(crate) fn decode_panasonic_v4(buf: &[u8], width: usize, height: usize, split: bool, dummy: bool) -> PixU16 {
|
||||
decode_threaded_multiline(
|
||||
width,
|
||||
height,
|
||||
5,
|
||||
dummy,
|
||||
&(|out: &mut [u16], row| {
|
||||
let skip = ((width * row * 9) + (width / 14 * 2 * row)) / 8;
|
||||
let blocks = skip / 0x4000;
|
||||
let src = &buf[blocks * 0x4000..];
|
||||
let mut pump = BitPumpPanasonic::new(src, split);
|
||||
for _ in 0..(skip % 0x4000) {
|
||||
pump.get_bits(8);
|
||||
}
|
||||
|
||||
let mut sh: i32 = 0;
|
||||
for out in out.chunks_exact_mut(14) {
|
||||
let mut pred: [i32; 2] = [0, 0];
|
||||
let mut nonz: [i32; 2] = [0, 0];
|
||||
|
||||
for i in 0..14 {
|
||||
if (i % 3) == 2 {
|
||||
sh = 4 >> (3 - pump.get_bits(2));
|
||||
}
|
||||
if nonz[i & 1] != 0 {
|
||||
let j = pump.get_bits(8) as i32;
|
||||
if j != 0 {
|
||||
pred[i & 1] -= 0x80 << sh;
|
||||
if pred[i & 1] < 0 || sh == 4 {
|
||||
pred[i & 1] &= !(-1 << sh);
|
||||
}
|
||||
pred[i & 1] += j << sh;
|
||||
}
|
||||
} else {
|
||||
nonz[i & 1] = pump.get_bits(8) as i32;
|
||||
if nonz[i & 1] != 0 || i > 11 {
|
||||
pred[i & 1] = (nonz[i & 1] << 4) | (pump.get_bits(4) as i32);
|
||||
}
|
||||
}
|
||||
out[i] = pred[i & 1] as u16;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
)
|
||||
.expect("Failed to decode") // Decoder should never fail
|
||||
}
|
||||
|
||||
pub struct BitPumpPanasonic<'a> {
|
||||
buffer: &'a [u8],
|
||||
pos: usize,
|
||||
nbits: u32,
|
||||
split: bool,
|
||||
}
|
||||
|
||||
impl<'a> BitPumpPanasonic<'a> {
|
||||
pub fn new(src: &'a [u8], split: bool) -> BitPumpPanasonic<'a> {
|
||||
BitPumpPanasonic {
|
||||
buffer: src,
|
||||
pos: 0,
|
||||
nbits: 0,
|
||||
split,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BitPump for BitPumpPanasonic<'a> {
|
||||
fn peek_bits(&mut self, num: u32) -> u32 {
|
||||
if num > self.nbits {
|
||||
self.nbits += 0x4000 * 8;
|
||||
self.pos += 0x4000;
|
||||
}
|
||||
let mut byte = ((self.nbits - num) >> 3) ^ 0x3ff0;
|
||||
if self.split {
|
||||
byte = (byte + 0x4000 - 0x2008) % 0x4000;
|
||||
}
|
||||
let bits = LEu16(self.buffer, byte as usize + self.pos - 0x4000) as u32;
|
||||
(bits >> ((self.nbits - num) & 7)) & (0x0ffffffffu32 >> (32 - num))
|
||||
}
|
||||
|
||||
fn consume_bits(&mut self, num: u32) {
|
||||
self.nbits -= num;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/// Ported from Libraw
|
||||
use crate::{
|
||||
decoders::*,
|
||||
pumps::{BitPump, BitPumpLSB},
|
||||
};
|
||||
|
||||
/// This works for 12 and 14 bit depth images
|
||||
pub(crate) fn decode_panasonic_v5(buf: &[u8], width: usize, height: usize, bps: u32, dummy: bool) -> PixU16 {
|
||||
// Raw data is divided into blocks of same size
|
||||
const V5_BLOCK_SIZE: usize = 0x4000;
|
||||
// Each block is splitted and swapped, we need to swap back
|
||||
const V5_SECTION_SPLIT_OFFSET: usize = 0x1FF8;
|
||||
// Each block contains multiple packets
|
||||
const V5_BYTES_PER_PACKET: usize = 16;
|
||||
// Count of packets in a block
|
||||
const V5_PACKETS_PER_BLOCK: usize = V5_BLOCK_SIZE / V5_BYTES_PER_PACKET;
|
||||
// Depending on bit depth, a packet forms different amount of pixels
|
||||
let pixels_per_packet = match bps {
|
||||
12 => 10,
|
||||
14 => 9,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Pixel count per full block
|
||||
let pixels_per_block = V5_PACKETS_PER_BLOCK * pixels_per_packet;
|
||||
|
||||
log::debug!("RW2 V5 decoder: pixels per block: {}, bps: {}", pixels_per_block, bps);
|
||||
|
||||
// We decode chunked at pixels_per_block boundary
|
||||
// Each block delivers the same amount of pixels.
|
||||
decode_threaded_chunked(
|
||||
width,
|
||||
height,
|
||||
pixels_per_block,
|
||||
dummy,
|
||||
&(|chunk, block_id| {
|
||||
// Block offset
|
||||
let src = &buf[block_id * V5_BLOCK_SIZE..block_id * V5_BLOCK_SIZE + V5_BLOCK_SIZE];
|
||||
// Now swap the two parts of the block
|
||||
let mut swapped = Vec::with_capacity(V5_BLOCK_SIZE);
|
||||
swapped.extend_from_slice(&src[V5_SECTION_SPLIT_OFFSET..]);
|
||||
swapped.extend_from_slice(&src[..V5_SECTION_SPLIT_OFFSET]);
|
||||
// Transform the packets into final pixels
|
||||
for (out, bytes) in chunk.chunks_exact_mut(pixels_per_packet).zip(swapped.chunks_exact(V5_BYTES_PER_PACKET)) {
|
||||
// The packet is a bitstream in LSB order.
|
||||
let mut pump = BitPumpLSB::new(bytes);
|
||||
out.iter_mut().for_each(|p| *p = pump.get_bits(bps) as u16);
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/// Ported from Libraw
|
||||
use crate::{
|
||||
decoders::*,
|
||||
pumps::{BitPump, BitPumpLSB},
|
||||
};
|
||||
|
||||
/// This works for 12 and 14 bit depth images
|
||||
pub(crate) fn decode_panasonic_v6(buf: &[u8], width: usize, height: usize, bps: u32, dummy: bool) -> PixU16 {
|
||||
log::debug!("width: {}", width);
|
||||
|
||||
const V6_BYTES_PER_BLOCK: usize = 16;
|
||||
|
||||
let pixels_per_block;
|
||||
|
||||
let pixelbase0;
|
||||
let pixelbase_compare;
|
||||
let spix_compare;
|
||||
let pixel_mask;
|
||||
|
||||
match bps {
|
||||
12 => {
|
||||
pixels_per_block = 14;
|
||||
pixelbase0 = 0x80;
|
||||
pixelbase_compare = 0x800;
|
||||
spix_compare = 0x3fff;
|
||||
pixel_mask = 0xfff;
|
||||
}
|
||||
14 => {
|
||||
pixels_per_block = 11;
|
||||
pixelbase0 = 0x200;
|
||||
pixelbase_compare = 0x2000;
|
||||
spix_compare = 0xffff;
|
||||
pixel_mask = 0x3fff;
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
let blocks_per_row = width / pixels_per_block;
|
||||
let bytes_per_row = V6_BYTES_PER_BLOCK * blocks_per_row;
|
||||
|
||||
assert_eq!(width % pixels_per_block, 0);
|
||||
|
||||
//log::debug!("RW2 V5 decoder: pixels per block: {}, bps: {}", pixels_per_block, bps);
|
||||
|
||||
// We decode chunked at pixels_per_block boundary
|
||||
// Each block delivers the same amount of pixels.
|
||||
decode_threaded(
|
||||
width,
|
||||
height,
|
||||
dummy,
|
||||
&(|out, row| {
|
||||
let src = &buf[row * bytes_per_row..row * bytes_per_row + bytes_per_row];
|
||||
for (block_id, block) in src.chunks_exact(V6_BYTES_PER_BLOCK).enumerate() {
|
||||
let out = &mut out[block_id * pixels_per_block..];
|
||||
let mut pixelbuffer = [0_u16; 18];
|
||||
|
||||
let mut pump = BitPumpLSB::new(block);
|
||||
|
||||
match bps {
|
||||
14 => {
|
||||
// We fill from reverse, because bitstream is reversed
|
||||
pump.get_bits(4); // padding bits, ignore it
|
||||
pixelbuffer[13] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[12] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[11] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[10] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[9] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[8] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[7] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[6] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[5] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[4] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[3] = pump.get_bits(10) as u16;
|
||||
pixelbuffer[2] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[1] = pump.get_bits(14) as u16;
|
||||
pixelbuffer[0] = pump.get_bits(14) as u16;
|
||||
}
|
||||
12 => {
|
||||
pixelbuffer[17] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[16] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[15] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[14] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[13] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[12] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[11] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[10] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[9] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[8] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[7] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[6] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[5] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[4] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[3] = pump.get_bits(8) as u16;
|
||||
pixelbuffer[2] = pump.get_bits(2) as u16;
|
||||
pixelbuffer[1] = pump.get_bits(12) as u16;
|
||||
pixelbuffer[0] = pump.get_bits(12) as u16;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let mut curr_pixel = 0;
|
||||
|
||||
let mut next_pixel = || -> u16 {
|
||||
curr_pixel += 1;
|
||||
pixelbuffer[curr_pixel - 1]
|
||||
};
|
||||
|
||||
let mut oddeven = [0, 0];
|
||||
let mut nonzero = [0, 0];
|
||||
let mut pmul = 0;
|
||||
let mut pixel_base = 0;
|
||||
|
||||
for pix in 0..pixels_per_block {
|
||||
if pix % 3 == 2 {
|
||||
let mut base = next_pixel();
|
||||
if base == 3 {
|
||||
base = 4;
|
||||
}
|
||||
pixel_base = pixelbase0 << base;
|
||||
pmul = 1 << base;
|
||||
}
|
||||
let mut epixel: u16 = next_pixel();
|
||||
if oddeven[pix % 2] != 0 {
|
||||
epixel *= pmul;
|
||||
if pixel_base < pixelbase_compare && nonzero[pix % 2] > pixel_base {
|
||||
epixel += nonzero[pix % 2] - pixel_base;
|
||||
}
|
||||
nonzero[pix % 2] = epixel;
|
||||
} else {
|
||||
oddeven[pix % 2] = epixel;
|
||||
if epixel != 0 {
|
||||
nonzero[pix % 2] = epixel;
|
||||
} else {
|
||||
epixel = nonzero[pix % 2];
|
||||
}
|
||||
}
|
||||
let spix = (epixel as i32).wrapping_sub(0xf);
|
||||
if spix <= spix_compare {
|
||||
out[pix] = (spix & spix_compare) as u16;
|
||||
} else {
|
||||
// FIXME: this is a convoluted way to compute zero.
|
||||
// What was this code trying to do, actually?
|
||||
epixel = ((epixel as i32).wrapping_add(0x7ffffff1) >> 0x1f) as u16;
|
||||
//epixel = static_cast<int>(epixel + 0x7ffffff1) >> 0x1f;
|
||||
//out(row, col) = epixel & 0x3fff;
|
||||
out[pix] = (epixel & pixel_mask) as u16;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
for (int pix = 0; pix < PanasonicV6Decompressor::PixelsPerBlock;
|
||||
pix++, col++) {
|
||||
if (pix % 3 == 2) {
|
||||
uint16_t base = page.nextpixel();
|
||||
if (base == 3)
|
||||
base = 4;
|
||||
pixel_base = 0x200 << base;
|
||||
pmul = 1 << base;
|
||||
}
|
||||
uint16_t epixel = page.nextpixel();
|
||||
if (oddeven[pix % 2]) {
|
||||
epixel *= pmul;
|
||||
if (pixel_base < 0x2000 && nonzero[pix % 2] > pixel_base)
|
||||
epixel += nonzero[pix % 2] - pixel_base;
|
||||
nonzero[pix % 2] = epixel;
|
||||
} else {
|
||||
oddeven[pix % 2] = epixel;
|
||||
if (epixel)
|
||||
nonzero[pix % 2] = epixel;
|
||||
else
|
||||
epixel = nonzero[pix % 2];
|
||||
}
|
||||
auto spix = static_cast<unsigned>(static_cast<int>(epixel) - 0xf);
|
||||
if (spix <= 0xffff)
|
||||
out(row, col) = spix & 0xffff;
|
||||
else {
|
||||
// FIXME: this is a convoluted way to compute zero.
|
||||
// What was this code trying to do, actually?
|
||||
epixel = static_cast<int>(epixel + 0x7ffffff1) >> 0x1f;
|
||||
out(row, col) = epixel & 0x3fff;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/// Ported from Libraw
|
||||
use crate::{
|
||||
decoders::*,
|
||||
pumps::{BitPump, BitPumpLSB},
|
||||
};
|
||||
|
||||
/// This works for 12 and 14 bit depth images
|
||||
pub(crate) fn decode_panasonic_v7(buf: &[u8], width: usize, height: usize, bps: u32, dummy: bool) -> PixU16 {
|
||||
const V7_BYTES_PER_BLOCK: usize = 16;
|
||||
|
||||
let pixels_per_block = match bps {
|
||||
14 => 9,
|
||||
12 => 10,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let blocks_per_row = width / pixels_per_block;
|
||||
|
||||
assert_eq!(width % pixels_per_block, 0);
|
||||
|
||||
let bytes_per_row = V7_BYTES_PER_BLOCK * blocks_per_row;
|
||||
|
||||
decode_threaded(
|
||||
width,
|
||||
height,
|
||||
dummy,
|
||||
&(|out, row| {
|
||||
let src = &buf[row * bytes_per_row..row * bytes_per_row + bytes_per_row];
|
||||
for (block_id, block) in src.chunks_exact(V7_BYTES_PER_BLOCK).enumerate() {
|
||||
let start = block_id * pixels_per_block;
|
||||
let out = &mut out[start..start + pixels_per_block];
|
||||
let mut pump = BitPumpLSB::new(block);
|
||||
out.iter_mut().for_each(|pixel| *pixel = pump.get_bits(14) as u16);
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1
|
||||
// Copyright 2024 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
// Originally written by LibRaw LLC
|
||||
// Copyright (C) 2022-2024 Alex Tutubalin, LibRaw LLC
|
||||
// Ported from C++ to Rust by Daniel Vogelbacher
|
||||
|
||||
use itertools::Itertools;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use crate::{
|
||||
alloc_image_ok,
|
||||
bits::{Endian, clamp},
|
||||
decoders::{Result, rw2::PanasonicTag},
|
||||
formats::tiff::IFD,
|
||||
pixarray::{PixU16, SharedPix2D},
|
||||
pumps::{BitPump, BitPumpReverseBitsMSB, ByteStream},
|
||||
rawsource::RawSource,
|
||||
};
|
||||
|
||||
/// Defines the offsets for the start of a strip.
|
||||
#[derive(Clone, Debug)]
|
||||
struct StripLineOffset {
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CF2Params {
|
||||
/// Unknown value, it's labeled as strip_height but don't match any height
|
||||
#[allow(dead_code)]
|
||||
strip_height: u32,
|
||||
|
||||
/// Unknown value, it's labeled as strip_width but don't match any width
|
||||
#[allow(dead_code)]
|
||||
strip_width: u32,
|
||||
|
||||
/// Gamma point and slope value.
|
||||
gamma_point: Vec<u32>,
|
||||
gamma_slope: Vec<u32>,
|
||||
|
||||
/// Max data value
|
||||
gamma_clip_val: u16,
|
||||
|
||||
/// Initial values (base) for huffman coding
|
||||
huf_init_val0: u16,
|
||||
|
||||
/// Initial values (base) for huffman coding
|
||||
huf_init_val1: u16,
|
||||
|
||||
/// Initial values (base) for huffman coding
|
||||
huf_init_val2: u16,
|
||||
|
||||
/// Initial values (base) for huffman coding
|
||||
huf_init_val3: u16,
|
||||
|
||||
/// Stored huffman table, usually 17 entries
|
||||
///
|
||||
/// This is a pair of (bitcnt, symbol).
|
||||
///
|
||||
/// Example table:
|
||||
/// 0000000 10 1022 11 2046 8 254 9 510
|
||||
/// 0000020 7 126 4 14 4 12 3 4
|
||||
/// 0000040 3 2 2 0 3 3 3 5
|
||||
/// 0000060 4 13 5 30 6 62 12 4094
|
||||
/// 0000100 12 4095
|
||||
/// 0000104
|
||||
huf_table: Vec<(u16, u16)>,
|
||||
|
||||
/// Shift down (0 in all samples...)
|
||||
huf_shift_down: Vec<u16>,
|
||||
|
||||
/// Number of H strips
|
||||
num_of_strips_h: u16,
|
||||
|
||||
/// Number of V strips
|
||||
num_of_strips_v: u16,
|
||||
|
||||
/// Offset to bitstream
|
||||
strip_byte_offsets: Vec<u32>,
|
||||
|
||||
/// Starting column offset in a output line
|
||||
strip_line_offsets: Vec<StripLineOffset>,
|
||||
|
||||
/// Size in bits of compressed bitstream
|
||||
strip_data_size: Vec<u32>,
|
||||
|
||||
/// Strip widths in pixels
|
||||
strip_widths: Vec<u16>,
|
||||
|
||||
/// Strip heights in pixels
|
||||
strip_heights: Vec<u16>,
|
||||
}
|
||||
|
||||
impl CF2Params {
|
||||
fn new(ifd: &IFD) -> Result<Self> {
|
||||
let strip_height = fetch_tiff_tag!(ifd, PanasonicTag::CF2StripHeight).force_u32(0);
|
||||
let gamma_point = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2Unknown1).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u32()).collect()
|
||||
};
|
||||
let gamma_slope = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2Unknown2).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u32()).collect()
|
||||
};
|
||||
let gamma_clip_val = fetch_tiff_tag!(ifd, PanasonicTag::CF2ClipVal).force_u16(0);
|
||||
let huf_init_val0 = fetch_tiff_tag!(ifd, PanasonicTag::CF2HufInitVal0).force_u16(0);
|
||||
let huf_init_val1 = fetch_tiff_tag!(ifd, PanasonicTag::CF2HufInitVal1).force_u16(0);
|
||||
let huf_init_val2 = fetch_tiff_tag!(ifd, PanasonicTag::CF2HufInitVal2).force_u16(0);
|
||||
let huf_init_val3 = fetch_tiff_tag!(ifd, PanasonicTag::CF2HufInitVal3).force_u16(0);
|
||||
let huf_table = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2HufTable).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| (bs.get_u16(), bs.get_u16())).collect()
|
||||
};
|
||||
let huf_shift_down = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2HufShiftDown).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u16()).collect()
|
||||
};
|
||||
let num_of_strips_h = fetch_tiff_tag!(ifd, PanasonicTag::CF2NumberOfStripsH).force_u16(0);
|
||||
let num_of_strips_v = fetch_tiff_tag!(ifd, PanasonicTag::CF2NumberOfStripsV).force_u16(0);
|
||||
let strip_byte_offsets = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2StripByteOffsets).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u32()).collect()
|
||||
};
|
||||
let strip_line_offsets = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2StripLineOffsets).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count)
|
||||
.map(|_| StripLineOffset {
|
||||
cols: bs.get_u16(),
|
||||
rows: bs.get_u16(),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let strip_data_size = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2StripDataSize).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u32()).collect()
|
||||
};
|
||||
let strip_widths = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2StripWidths).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u16()).collect()
|
||||
};
|
||||
let strip_heights = {
|
||||
let mut bs = ByteStream::new(fetch_tiff_tag!(ifd, PanasonicTag::CF2StripHeights).get_data(), Endian::Little);
|
||||
let count = bs.get_u16();
|
||||
(0..count).map(|_| bs.get_u16()).collect()
|
||||
};
|
||||
let strip_width = fetch_tiff_tag!(ifd, PanasonicTag::CF2StripWidth).force_u32(0);
|
||||
|
||||
Ok(Self {
|
||||
strip_height,
|
||||
gamma_point,
|
||||
gamma_slope,
|
||||
gamma_clip_val,
|
||||
huf_init_val0,
|
||||
huf_init_val1,
|
||||
huf_init_val2,
|
||||
huf_init_val3,
|
||||
huf_table,
|
||||
huf_shift_down,
|
||||
num_of_strips_h,
|
||||
num_of_strips_v,
|
||||
strip_byte_offsets,
|
||||
strip_line_offsets,
|
||||
strip_data_size,
|
||||
strip_widths,
|
||||
strip_heights,
|
||||
strip_width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct HuffmanSymbol {
|
||||
/// Length of the symbol in bits
|
||||
bitcnt: u8,
|
||||
/// Actual Huffman symbol, right-padded with 0 bits
|
||||
symbol: u16,
|
||||
/// Pre-calculated bitmask, actually (-1) << (16-bits)
|
||||
mask: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct HuffmanDecoder {
|
||||
#[allow(dead_code)]
|
||||
huff_symbols: [HuffmanSymbol; 17],
|
||||
|
||||
/// Lookup cache for all possible u16 values.
|
||||
/// If a 16 bit input value is invalid (the symbol is undefined), the
|
||||
/// value is None, otherwise it's (bitcnt, ssss)
|
||||
cache: Vec<Option<(u8, u8)>>,
|
||||
}
|
||||
|
||||
impl HuffmanDecoder {
|
||||
fn new(symlens: impl Iterator<Item = u8>, mut symbols: impl Iterator<Item = u16>) -> Self {
|
||||
let mut huff_symbols: [HuffmanSymbol; 17] = Default::default();
|
||||
for (i, symlen) in symlens.enumerate() {
|
||||
let symbol = symbols.next().expect("symbol iterator is shorter than symlens iterator");
|
||||
let bitmask = 0xFFFFu16 >> (16 - symlen);
|
||||
debug_assert_eq!(symbol, symbol & bitmask);
|
||||
// Left-align symbol and mask
|
||||
huff_symbols[i] = HuffmanSymbol {
|
||||
bitcnt: symlen,
|
||||
symbol: (symbol) << (16 - symlen),
|
||||
mask: 0xFFFF << (16 - symlen),
|
||||
};
|
||||
}
|
||||
|
||||
// Generate lookup cache for all possible 16 bit input values.
|
||||
let cache = (0..=0xFFFFu16)
|
||||
.map(|x| Self::slow_lookup(&huff_symbols, x).map(|ssss| (huff_symbols[ssss as usize].bitcnt, ssss)))
|
||||
.collect_vec();
|
||||
Self { huff_symbols, cache }
|
||||
}
|
||||
|
||||
/// Slow lookup into Huffman symbol table
|
||||
fn slow_lookup(huff_symbols: &[HuffmanSymbol; 17], bits: u16) -> Option<u8> {
|
||||
for i in 0..17 {
|
||||
if (bits & huff_symbols[i].mask) == huff_symbols[i].symbol {
|
||||
return Some(i as u8);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract Huffman symbol from bitstream pump and
|
||||
/// return index into symbol table.
|
||||
fn get_next(&self, pump: &mut dyn BitPump) -> u8 {
|
||||
let next_bits = pump.peek_bits(16);
|
||||
debug_assert_eq!(self.cache.len(), u16::MAX as usize + 1);
|
||||
if let Some((bits, ssss)) = unsafe { *self.cache.get_unchecked(next_bits as usize) } {
|
||||
pump.consume_bits(bits as u32);
|
||||
ssss
|
||||
} else {
|
||||
panic!("Input value {:016b} starts not with a valid huffman symbol", next_bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal decoder state
|
||||
struct State {
|
||||
huffdec: HuffmanDecoder,
|
||||
gamma_table: Option<Vec<u16>>,
|
||||
datamax: i32,
|
||||
line_base: CoeffBase,
|
||||
current_base: CoeffBase,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(params: &CF2Params) -> Self {
|
||||
let initial = [params.huf_init_val0, params.huf_init_val1, params.huf_init_val2, params.huf_init_val3];
|
||||
let gamma_table = make_gammatable(params);
|
||||
|
||||
let line_base = CoeffBase::new([initial[0], initial[1], initial[2], initial[3]]);
|
||||
let current_base = line_base;
|
||||
|
||||
let huffdec = HuffmanDecoder::new(
|
||||
params.huf_table.iter().map(|(bits, _symbol)| *bits as u8),
|
||||
params.huf_table.iter().map(|(_bits, symbol)| *symbol),
|
||||
);
|
||||
|
||||
Self {
|
||||
huffdec,
|
||||
gamma_table,
|
||||
datamax: params.gamma_clip_val as i32,
|
||||
line_base,
|
||||
current_base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct CoeffBase {
|
||||
coeff: [u16; 4],
|
||||
}
|
||||
|
||||
impl CoeffBase {
|
||||
pub fn new(coeff: [u16; 4]) -> Self {
|
||||
Self { coeff }
|
||||
}
|
||||
|
||||
pub fn update(&mut self, line: &[u16]) {
|
||||
self.coeff = [line[0], line[1], line[2], line[3]];
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_gamma(params: &CF2Params, idx: u32) -> u16 {
|
||||
let gamma_base = 0;
|
||||
let gamma_points = ¶ms.gamma_point; // [65536, 65536, 65536, 65536, 65536, 65536]
|
||||
let gamma_slopes = ¶ms.gamma_slope; // 0
|
||||
let clipping = params.gamma_clip_val;
|
||||
|
||||
let mut x = {
|
||||
let mut tmp: u32 = idx | 0xFFFF0000;
|
||||
if (idx & 0x10000) == 0 {
|
||||
tmp = idx & 0x1FFFF;
|
||||
}
|
||||
u32::min(gamma_base + tmp, 0xFFFF)
|
||||
};
|
||||
|
||||
let mut idx = 0;
|
||||
if (x & 0x80000000) != 0 {
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if x >= (0xFFFF & gamma_slopes[1]) {
|
||||
idx = 1;
|
||||
if x >= (0xFFFF & gamma_slopes[2]) {
|
||||
idx = 2;
|
||||
if x >= (0xFFFF & gamma_slopes[3]) {
|
||||
idx = 3;
|
||||
if x >= (0xFFFF & gamma_slopes[4]) {
|
||||
idx = (((x as u64 | 0x5_00000000u64) - (0xFFFF & gamma_slopes[5]) as u64) >> 32) as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let point = gamma_points[idx];
|
||||
let slope = gamma_slopes[idx];
|
||||
let mut tmp: u32 = x - (slope & 0xFFFF);
|
||||
let result: u16;
|
||||
|
||||
if (point & 0x1F) == 31 {
|
||||
result = if idx == 5 { 0xFFFF } else { (gamma_slopes[idx + 1] >> 16) & 0xFFFF } as u16;
|
||||
return u16::min(result, clipping);
|
||||
}
|
||||
if (point & 0x10) == 0 {
|
||||
if (point & 0x1F) == 15 {
|
||||
let result = ((slope >> 16) & 0xFFFF) as u16;
|
||||
return u16::min(result, clipping);
|
||||
} else if (point & 0x1F) != 0 {
|
||||
tmp = (tmp + (1 << ((point & 0x1F) - 1))) >> (point & 0x1F);
|
||||
}
|
||||
} else {
|
||||
tmp <<= point & 0xF;
|
||||
}
|
||||
|
||||
result = (tmp + ((slope >> 16) & 0xFFFF)) as u16;
|
||||
u16::min(result, clipping)
|
||||
}
|
||||
|
||||
fn make_gammatable(params: &CF2Params) -> Option<Vec<u16>> {
|
||||
const LINEAR_POINTS: [u32; 6] = [65536; 6];
|
||||
const LINEAR_SLOPES: [u32; 6] = [0; 6];
|
||||
|
||||
if params.gamma_point == LINEAR_POINTS && params.gamma_slope == LINEAR_SLOPES {
|
||||
None
|
||||
} else {
|
||||
let mut table = vec![0; 0x10000];
|
||||
let mut _linear = true;
|
||||
for idx in 0..0x10000 {
|
||||
table[idx] = calc_gamma(params, idx as u32);
|
||||
_linear = _linear && table[idx] == idx as u16;
|
||||
}
|
||||
Some(table)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode Panasonic V8 bitstreams
|
||||
pub(crate) fn decode_panasonic_v8(rawfile: &RawSource, width: usize, height: usize, _bps: u32, ifd: &IFD, dummy: bool) -> Result<PixU16> {
|
||||
let out = alloc_image_ok!(width, height, dummy);
|
||||
|
||||
let params = CF2Params::new(ifd)?;
|
||||
log::debug!("pana8: params: {:?}", params);
|
||||
|
||||
assert_eq!(
|
||||
params.strip_widths.iter().map(|x| *x as i32).sum::<i32>() as usize / params.num_of_strips_v as usize,
|
||||
width
|
||||
);
|
||||
|
||||
// Shared output buffer, we need to write from multiple rayon threads to output image.
|
||||
let shared_pix = SharedPix2D::new(out);
|
||||
|
||||
let total_strip_count = (params.num_of_strips_h * params.num_of_strips_v) as usize;
|
||||
let mut bitstreams = Vec::with_capacity(total_strip_count);
|
||||
for strip_id in 0..total_strip_count {
|
||||
bitstreams.push(rawfile.subview(params.strip_byte_offsets[strip_id] as u64, (params.strip_data_size[strip_id] as u64 + 7) / 8)?);
|
||||
}
|
||||
|
||||
// Parallel decode multiple strips
|
||||
(0..total_strip_count).into_par_iter().for_each(|strip_id| {
|
||||
let buf = &bitstreams[strip_id];
|
||||
decode_strip(buf, ¶ms, strip_id, unsafe { shared_pix.inner_mut() });
|
||||
});
|
||||
Ok(shared_pix.into_inner())
|
||||
}
|
||||
|
||||
/// Decode a single strip
|
||||
fn decode_strip(buf: &[u8], params: &CF2Params, strip_id: usize, out: &mut PixU16) {
|
||||
let mut pump = BitPumpReverseBitsMSB::new(buf);
|
||||
let width = params.strip_widths[strip_id] as usize;
|
||||
let height = params.strip_heights[strip_id] as usize;
|
||||
let halfheight = height >> 1;
|
||||
let halfwidth = width >> 1;
|
||||
let doublewidth = halfwidth * 4;
|
||||
let mut linebuf = vec![0_u16; doublewidth];
|
||||
|
||||
// for (i, item) in params.huf_table.iter().enumerate() {
|
||||
// let fmt = format!("{:#032b}", item.1);
|
||||
// log::debug!("Pana8 huf_table {i}: {} (bits: {})", fmt.split_at((32 - item.0) as usize).1, item.0);
|
||||
// //log::debug!("Pana8: huf_table {:02}: {}, {}", i, item.0, item.1);
|
||||
// }
|
||||
|
||||
let mut state = State::new(params);
|
||||
|
||||
// Data is encoded in RGRGRG..GBGBGB in a single line (like LJPEG92 4-7 predictors)
|
||||
for curr_row in 0..halfheight {
|
||||
state.current_base = state.line_base;
|
||||
for col in 0..doublewidth {
|
||||
// Calculate index
|
||||
let ssss = state.huffdec.get_next(&mut pump);
|
||||
|
||||
// Shiftdown seems to be the count of bits shifted to right during encoding.
|
||||
// It's 0 for all existing samples so far, highly interested in samples that has
|
||||
let shift_down: u8 = (params.huf_shift_down[ssss as usize] & 0x1F) as u8;
|
||||
assert_eq!(shift_down, 0, "CF2HufShiftDown samples required");
|
||||
|
||||
// Calculate total required bits to read from bitstream.
|
||||
let req_bits: u32 = ssss.saturating_sub(shift_down as u8) as u32;
|
||||
let delta1: i32 = if req_bits == 0 {
|
||||
0
|
||||
} else {
|
||||
debug_assert_ne!(req_bits, 0);
|
||||
let rawbits: u32 = pump.get_bits(req_bits as u32); // Get additional bits
|
||||
let sign = rawbits >> (req_bits - 1); // Get leading sign bit
|
||||
let val = (rawbits << (params.huf_shift_down[ssss as usize] & 0xFF)) as i32;
|
||||
|
||||
if sign == 1 {
|
||||
val
|
||||
} else if ssss > 0 {
|
||||
if shift_down != 0 { val + (-1 << ssss) } else { val + (-1 << ssss) + 1 }
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let delta2 = if shift_down != 0 { 1 << (shift_down - 1) } else { 0 };
|
||||
let delta = delta1 + delta2;
|
||||
|
||||
// For each col iteration, we write to ONE pixel of of 4-pixel group.
|
||||
let destpixel = &mut linebuf[col & !0x3..];
|
||||
|
||||
if col & 3 == 2 {
|
||||
let val = state.current_base.coeff[1] as i32 + delta;
|
||||
destpixel[1] = clamp(val, 0, state.datamax) as u16;
|
||||
} else if col & 3 == 1 {
|
||||
let val = state.current_base.coeff[2] as i32 + delta;
|
||||
destpixel[2] = clamp(val, 0, state.datamax) as u16;
|
||||
} else if col & 3 != 0 {
|
||||
let val = state.current_base.coeff[3] as i32 + delta;
|
||||
destpixel[3] = clamp(val, 0, state.datamax) as u16;
|
||||
} else {
|
||||
let val = state.current_base.coeff[0] as i32 + delta;
|
||||
destpixel[0] = clamp(val, 0, state.datamax) as u16;
|
||||
}
|
||||
|
||||
if col & 3 == 3 {
|
||||
state.current_base.update(destpixel);
|
||||
}
|
||||
if col == 3 {
|
||||
// base for next line (col == 3 -> first 4 pixels are complete in current row)
|
||||
state.line_base.update(&linebuf);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy line buffer into output image.
|
||||
// Line buffer contains two rows packed into one row with double width.
|
||||
assert_eq!(linebuf.len(), 2 * width);
|
||||
for col in (0..width).step_by(2) {
|
||||
let row_offset = params.strip_line_offsets[strip_id].rows as usize;
|
||||
let left_margin = params.strip_line_offsets[strip_id].cols as usize;
|
||||
let dest_row = row_offset + (curr_row * 2);
|
||||
if let Some(gamma_table) = &state.gamma_table {
|
||||
debug_assert!(!gamma_table.is_empty());
|
||||
*out.at_mut(dest_row + 0, left_margin + col + 0) = gamma_table[linebuf[2 * col + 0] as usize];
|
||||
*out.at_mut(dest_row + 0, left_margin + col + 1) = gamma_table[linebuf[2 * col + 1] as usize];
|
||||
*out.at_mut(dest_row + 1, left_margin + col + 0) = gamma_table[linebuf[2 * col + 2] as usize];
|
||||
*out.at_mut(dest_row + 1, left_margin + col + 1) = gamma_table[linebuf[2 * col + 3] as usize];
|
||||
} else {
|
||||
*out.at_mut(dest_row + 0, left_margin + col + 0) = linebuf[2 * col + 0];
|
||||
*out.at_mut(dest_row + 0, left_margin + col + 1) = linebuf[2 * col + 1];
|
||||
*out.at_mut(dest_row + 1, left_margin + col + 0) = linebuf[2 * col + 2];
|
||||
*out.at_mut(dest_row + 1, left_margin + col + 1) = linebuf[2 * col + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
use log::warn;
|
||||
use std::cmp;
|
||||
|
||||
use crate::RawImage;
|
||||
use crate::RawLoader;
|
||||
use crate::RawlerError;
|
||||
use crate::Result;
|
||||
use crate::alloc_image;
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::bits::LEu32;
|
||||
use crate::bits::clampbits;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::GenericTiffReader;
|
||||
use crate::formats::tiff::IFD;
|
||||
use crate::formats::tiff::ifd::OffsetMode;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::lens::LensDescription;
|
||||
use crate::lens::LensResolver;
|
||||
use crate::packed::decode_12be;
|
||||
use crate::packed::decode_12le;
|
||||
use crate::packed::decode_12le_unpacked;
|
||||
use crate::packed::decode_14le_unpacked;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::pumps::BitPump;
|
||||
use crate::pumps::BitPumpMSB;
|
||||
use crate::pumps::BitPumpMSB32;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::ExifTag;
|
||||
use crate::tags::TiffCommonTag;
|
||||
|
||||
use super::Camera;
|
||||
use super::Decoder;
|
||||
use super::FormatHint;
|
||||
use super::RawDecodeParams;
|
||||
use super::RawMetadata;
|
||||
use super::ok_cfa_image_with_blacklevels;
|
||||
|
||||
const NX_MOUNT: &str = "NX-mount";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SrwDecoder<'a> {
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
makernote: IFD,
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl<'a> SrwDecoder<'a> {
|
||||
pub fn new(file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<SrwDecoder<'a>> {
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
|
||||
let makernote = if let Some(exif) = tiff.find_first_ifd_with_tag(ExifTag::MakerNotes) {
|
||||
exif.parse_makernote(&mut file.reader(), OffsetMode::RelativeToIFD, &[])?
|
||||
} else {
|
||||
warn!("SRW makernote not found");
|
||||
None
|
||||
}
|
||||
.ok_or("File has not makernotes")?;
|
||||
|
||||
Ok(SrwDecoder {
|
||||
tiff,
|
||||
rawloader,
|
||||
camera,
|
||||
makernote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for SrwDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with StripOffsets tag")))?;
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
let compression = fetch_tiff_tag!(raw, TiffCommonTag::Compression).force_u32(0);
|
||||
let bits = fetch_tiff_tag!(raw, TiffCommonTag::BitsPerSample).force_u32(0);
|
||||
let src = file.subview_until_eof_padded(offset as u64)?;
|
||||
|
||||
let image = match compression {
|
||||
32769 => match bits {
|
||||
12 => decode_12le_unpacked(&src, width, height, dummy),
|
||||
14 => decode_14le_unpacked(&src, width, height, dummy),
|
||||
x => return Err(RawlerError::unsupported(&self.camera, format!("SRW: Don't know how to handle bps {}", x))),
|
||||
},
|
||||
32770 => match raw.get_entry(TiffCommonTag::SrwSensorAreas) {
|
||||
None => match bits {
|
||||
12 => {
|
||||
if self.camera.find_hint("little_endian") {
|
||||
decode_12le(&src, width, height, dummy)
|
||||
} else {
|
||||
decode_12be(&src, width, height, dummy)
|
||||
}
|
||||
}
|
||||
14 => decode_14le_unpacked(&src, width, height, dummy),
|
||||
x => return Err(RawlerError::unsupported(&self.camera, format!("SRW: Don't know how to handle bps {}", x))),
|
||||
},
|
||||
Some(x) => {
|
||||
let coffset = x.force_usize(0);
|
||||
assert!(coffset > 0, "Surely this can't be the start of the file");
|
||||
let loffsets = file.subview_until_eof(coffset as u64)?;
|
||||
SrwDecoder::decode_srw1(&src, loffsets, width, height, dummy)
|
||||
}
|
||||
},
|
||||
32772 => SrwDecoder::decode_srw2(&src, width, height, dummy),
|
||||
32773 => SrwDecoder::decode_srw3(&src, width, height, dummy),
|
||||
x => {
|
||||
return Err(RawlerError::unsupported(
|
||||
&self.camera,
|
||||
format!("SRW: Don't know how to handle compression {}", x),
|
||||
));
|
||||
}
|
||||
};
|
||||
let cpp = 1;
|
||||
ok_cfa_image_with_blacklevels(self.camera.clone(), cpp, self.get_wb()?, self.get_blacklevel()?, image, dummy)
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::SRW
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SrwDecoder<'a> {
|
||||
pub fn decode_srw1(buf: &[u8], loffsets: &[u8], width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
|
||||
for row in 0..height {
|
||||
let mut len: [u32; 4] = [if row < 2 { 7 } else { 4 }; 4];
|
||||
let loffset = LEu32(loffsets, row * 4) as usize;
|
||||
let mut pump = BitPumpMSB32::new(&buf[loffset..]);
|
||||
|
||||
let img = width * row;
|
||||
let img_up = width * (cmp::max(1, row) - 1);
|
||||
let img_up2 = width * (cmp::max(2, row) - 2);
|
||||
|
||||
// Image is arranged in groups of 16 pixels horizontally
|
||||
for col in (0..width).step_by(16) {
|
||||
let dir = pump.get_bits(1) == 1;
|
||||
|
||||
let ops = [pump.get_bits(2), pump.get_bits(2), pump.get_bits(2), pump.get_bits(2)];
|
||||
for (i, op) in ops.iter().enumerate() {
|
||||
match *op {
|
||||
3 => len[i] = pump.get_bits(4),
|
||||
2 => len[i] -= 1,
|
||||
1 => len[i] += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// First decode even pixels
|
||||
for c in (0..16).step_by(2) {
|
||||
let l = len[c >> 3];
|
||||
let adj = pump.get_ibits_sextended(l);
|
||||
let predictor = if dir {
|
||||
// Upward prediction
|
||||
out[img_up + col + c]
|
||||
} else {
|
||||
// Left to right prediction
|
||||
if col == 0 { 128 } else { out[img + col - 2] }
|
||||
};
|
||||
if col + c < width {
|
||||
// No point in decoding pixels outside the image
|
||||
out[img + col + c] = ((predictor as i32) + adj) as u16;
|
||||
}
|
||||
}
|
||||
// Now decode odd pixels
|
||||
for c in (1..16).step_by(2) {
|
||||
let l = len[2 | (c >> 3)];
|
||||
let adj = pump.get_ibits_sextended(l);
|
||||
let predictor = if dir {
|
||||
// Upward prediction
|
||||
out[img_up2 + col + c]
|
||||
} else {
|
||||
// Left to right prediction
|
||||
if col == 0 { 128 } else { out[img + col - 1] }
|
||||
};
|
||||
if col + c < width {
|
||||
// No point in decoding pixels outside the image
|
||||
out[img + col + c] = ((predictor as i32) + adj) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SRW1 apparently has red and blue swapped, just changing the CFA pattern to
|
||||
// match causes color fringing in high contrast areas because the actual pixel
|
||||
// locations would not match the CFA pattern
|
||||
for row in (0..height).step_by(2) {
|
||||
for col in (0..width).step_by(2) {
|
||||
out.pixels_mut().swap(row * width + col + 1, (row + 1) * width + col);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn decode_srw2(buf: &[u8], width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
|
||||
// This format has a variable length encoding of how many bits are needed
|
||||
// to encode the difference between pixels, we use a table to process it
|
||||
// that has two values, the first the number of bits that were used to
|
||||
// encode, the second the number of bits that come after with the difference
|
||||
// The table has 14 entries because the difference can have between 0 (no
|
||||
// difference) and 13 bits (differences between 12 bits numbers can need 13)
|
||||
let tab: [[u32; 2]; 14] = [
|
||||
[3, 4],
|
||||
[3, 7],
|
||||
[2, 6],
|
||||
[2, 5],
|
||||
[4, 3],
|
||||
[6, 0],
|
||||
[7, 9],
|
||||
[8, 10],
|
||||
[9, 11],
|
||||
[10, 12],
|
||||
[10, 13],
|
||||
[5, 1],
|
||||
[4, 8],
|
||||
[4, 2],
|
||||
];
|
||||
|
||||
// We generate a 1024 entry table (to be addressed by reading 10 bits) by
|
||||
// consecutively filling in 2^(10-N) positions where N is the variable number of
|
||||
// bits of the encoding. So for example 4 is encoded with 3 bits so the first
|
||||
// 2^(10-3)=128 positions are set with 3,4 so that any time we read 000 we
|
||||
// know the next 4 bits are the difference. We read 10 bits because that is
|
||||
// the maximum number of bits used in the variable encoding (for the 12 and
|
||||
// 13 cases)
|
||||
let mut tbl: [[u32; 2]; 1024] = [[0, 0]; 1024];
|
||||
let mut n: usize = 0;
|
||||
for i in 0..14 {
|
||||
let mut c = 0;
|
||||
while c < (1024 >> tab[i][0]) {
|
||||
tbl[n][0] = tab[i][0];
|
||||
tbl[n][1] = tab[i][1];
|
||||
n += 1;
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut vpred: [[i32; 2]; 2] = [[0, 0], [0, 0]];
|
||||
let mut hpred: [i32; 2] = [0, 0];
|
||||
let mut pump = BitPumpMSB::new(buf);
|
||||
for row in 0..height {
|
||||
for col in 0..width {
|
||||
let diff = SrwDecoder::srw2_diff(&mut pump, &tbl);
|
||||
if col < 2 {
|
||||
vpred[row & 1][col] += diff;
|
||||
hpred[col] = vpred[row & 1][col];
|
||||
} else {
|
||||
hpred[col & 1] += diff;
|
||||
}
|
||||
out[row * width + col] = hpred[col & 1] as u16;
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn srw2_diff(pump: &mut BitPumpMSB, tbl: &[[u32; 2]; 1024]) -> i32 {
|
||||
// We read 10 bits to index into our table
|
||||
let c = pump.peek_bits(10);
|
||||
// Skip the bits that were used to encode this case
|
||||
pump.consume_bits(tbl[c as usize][0]);
|
||||
// Read the number of bits the table tells me
|
||||
let len = tbl[c as usize][1];
|
||||
let mut diff = pump.get_bits(len) as i32;
|
||||
// If the first bit is 0 we need to turn this into a negative number
|
||||
if len != 0 && (diff & (1 << (len - 1))) == 0 {
|
||||
diff -= (1 << len) - 1;
|
||||
}
|
||||
diff
|
||||
}
|
||||
|
||||
pub fn decode_srw3(buf: &[u8], width: usize, height: usize, dummy: bool) -> PixU16 {
|
||||
// Decoder for third generation compressed SRW files (NX1)
|
||||
// Seriously Samsung just use lossless jpeg already, it compresses better too :)
|
||||
|
||||
// Thanks to Michael Reichmann (Luminous Landscape) for putting me in contact
|
||||
// and Loring von Palleske (Samsung) for pointing to the open-source code of
|
||||
// Samsung's DNG converter at http://opensource.samsung.com/
|
||||
|
||||
let mut out = alloc_image!(width, height, dummy);
|
||||
let mut pump = BitPumpMSB32::new(buf);
|
||||
|
||||
// Process the initial metadata bits, we only really use initVal, width and
|
||||
// height (the last two match the TIFF values anyway)
|
||||
pump.get_bits(16); // NLCVersion
|
||||
pump.get_bits(4); // ImgFormat
|
||||
let bit_depth = pump.get_bits(4) + 1;
|
||||
pump.get_bits(4); // NumBlkInRCUnit
|
||||
pump.get_bits(4); // CompressionRatio
|
||||
pump.get_bits(16); // Width;
|
||||
pump.get_bits(16); // Height;
|
||||
pump.get_bits(16); // TileWidth
|
||||
pump.get_bits(4); // reserved
|
||||
|
||||
// The format includes an optimization code that sets 3 flags to change the
|
||||
// decoding parameters
|
||||
let optflags = pump.get_bits(4);
|
||||
static OPT_SKIP: u32 = 1; // Skip checking if we need differences from previous line
|
||||
static OPT_MV: u32 = 2; // Simplify motion vector definition
|
||||
static OPT_QP: u32 = 4; // Don't scale the diff values
|
||||
|
||||
pump.get_bits(8); // OverlapWidth
|
||||
pump.get_bits(8); // reserved
|
||||
pump.get_bits(8); // Inc
|
||||
pump.get_bits(2); // reserved
|
||||
let init_val = pump.get_bits(14) as u16;
|
||||
|
||||
// The format is relatively straightforward. Each line gets encoded as a set
|
||||
// of differences from pixels from another line. Pixels are grouped in blocks
|
||||
// of 16 (8 green, 8 red or blue). Each block is encoded in three sections.
|
||||
// First 1 or 4 bits to specify which reference pixels to use, then a section
|
||||
// that specifies for each pixel the number of bits in the difference, then
|
||||
// the actual difference bits
|
||||
let mut line_offset = 0;
|
||||
for row in 0..height {
|
||||
line_offset += pump.get_pos();
|
||||
// Align pump to 16byte boundary
|
||||
if (line_offset & 0x0f) != 0 {
|
||||
line_offset += 16 - (line_offset & 0xf);
|
||||
}
|
||||
pump = BitPumpMSB32::new(&buf[line_offset..]);
|
||||
|
||||
let img = width * row;
|
||||
let img_up = width * (cmp::max(1, row) - 1);
|
||||
let img_up2 = width * (cmp::max(2, row) - 2);
|
||||
|
||||
// Initialize the motion and diff modes at the start of the line
|
||||
let mut motion: usize = 7;
|
||||
// By default we are not scaling values at all
|
||||
let mut scale: i32 = 0;
|
||||
let mut diff_bits_mode: [[u32; 2]; 3] = [[0; 2]; 3];
|
||||
for i in 0..3 {
|
||||
let init: u32 = if row < 2 { 7 } else { 4 };
|
||||
diff_bits_mode[i][0] = init;
|
||||
diff_bits_mode[i][1] = init;
|
||||
}
|
||||
|
||||
for col in (0..width).step_by(16) {
|
||||
// Calculate how much scaling the final values will need
|
||||
scale = if (optflags & OPT_QP) == 0 && (col & 63) == 0 {
|
||||
let scalevals: [i32; 3] = [0, -2, 2];
|
||||
let i = pump.get_bits(2) as usize;
|
||||
if i < 3 { scale + scalevals[i] } else { pump.get_bits(12) as i32 }
|
||||
} else {
|
||||
scale // Keep value from previous iteration
|
||||
};
|
||||
|
||||
// First we figure out which reference pixels mode we're in
|
||||
if (optflags & OPT_MV) != 0 {
|
||||
motion = if pump.get_bits(1) != 0 { 3 } else { 7 };
|
||||
} else if pump.get_bits(1) == 0 {
|
||||
motion = pump.get_bits(3) as usize;
|
||||
}
|
||||
|
||||
if row < 2 && motion != 7 {
|
||||
panic!("SRW Decoder: At start of image and motion isn't 7. File corrupted?")
|
||||
}
|
||||
|
||||
if motion == 7 {
|
||||
// The base case, just set all pixels to the previous ones on the same line
|
||||
// If we're at the left edge we just start at the initial value
|
||||
for i in 0..16 {
|
||||
out[img + col + i] = if col == 0 { init_val } else { out[img + col + i - 2] };
|
||||
}
|
||||
} else {
|
||||
// The complex case, we now need to actually lookup one or two lines above
|
||||
if row < 2 {
|
||||
panic!("SRW: Got a previous line lookup on first two lines. File corrupted?");
|
||||
}
|
||||
let motion_offset: [isize; 7] = [-4, -2, -2, 0, 0, 2, 4];
|
||||
let motion_average: [i32; 7] = [0, 0, 1, 0, 1, 0, 0];
|
||||
let slide_offset = motion_offset[motion];
|
||||
|
||||
for i in 0..16 {
|
||||
let refpixel: usize = if ((row + i) & 0x1) != 0 {
|
||||
// Red or blue pixels use same color two lines up
|
||||
((img_up2 + col + i) as isize + slide_offset) as usize
|
||||
} else {
|
||||
// Green pixel N uses Green pixel N from row above (top left or top right)
|
||||
if (i % 2) != 0 {
|
||||
((img_up + col + i - 1) as isize + slide_offset) as usize
|
||||
} else {
|
||||
((img_up + col + i + 1) as isize + slide_offset) as usize
|
||||
}
|
||||
};
|
||||
// In some cases we use as reference interpolation of this pixel and the next
|
||||
out[img + col + i] = if motion_average[motion] != 0 {
|
||||
(out[refpixel] + out[refpixel + 2] + 1) >> 1
|
||||
} else {
|
||||
out[refpixel]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out how many difference bits we have to read for each pixel
|
||||
let mut diff_bits: [u32; 4] = [0; 4];
|
||||
if (optflags & OPT_SKIP) != 0 || pump.get_bits(1) == 0 {
|
||||
let flags: [u32; 4] = [pump.get_bits(2), pump.get_bits(2), pump.get_bits(2), pump.get_bits(2)];
|
||||
for i in 0..4 {
|
||||
// The color is 0-Green 1-Blue 2-Red
|
||||
let colornum: usize = if row % 2 != 0 { i >> 1 } else { ((i >> 1) + 2) % 3 };
|
||||
match flags[i] {
|
||||
0 => {
|
||||
diff_bits[i] = diff_bits_mode[colornum][0];
|
||||
}
|
||||
1 => {
|
||||
diff_bits[i] = diff_bits_mode[colornum][0] + 1;
|
||||
}
|
||||
2 => {
|
||||
diff_bits[i] = diff_bits_mode[colornum][0] - 1;
|
||||
}
|
||||
3 => {
|
||||
diff_bits[i] = pump.get_bits(4);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
diff_bits_mode[colornum][0] = diff_bits_mode[colornum][1];
|
||||
diff_bits_mode[colornum][1] = diff_bits[i];
|
||||
if diff_bits[i] > bit_depth + 1 {
|
||||
panic!("SRW Decoder: Too many difference bits. File corrupted?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actually read the differences and write them to the pixels
|
||||
for i in 0..16 {
|
||||
let len = diff_bits[i >> 2];
|
||||
let mut diff = pump.get_ibits_sextended(len);
|
||||
diff = diff * (scale * 2 + 1) + scale;
|
||||
|
||||
// Apply the diff to pixels 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15
|
||||
let pos = if row % 2 != 0 {
|
||||
((i & 0x7) << 1) + 1 - (i >> 3)
|
||||
} else {
|
||||
((i & 0x7) << 1) + (i >> 3)
|
||||
} + img
|
||||
+ col;
|
||||
out[pos] = clampbits((out[pos] as i32) + diff, bit_depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
if let Some(lens_id) = self.makernote.get_entry(SrwMakernote::LensModel) {
|
||||
let lens_id = lens_id.force_u16(0);
|
||||
let resolver = LensResolver::new()
|
||||
.with_lens_id((lens_id.into(), 0))
|
||||
.with_camera(&self.camera)
|
||||
.with_mounts(&[NX_MOUNT.into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let rggb_levels = fetch_tiff_tag!(self.makernote, SrwMakernote::SrwRGGBLevels);
|
||||
let rggb_blacks = fetch_tiff_tag!(self.makernote, SrwMakernote::SrwRGGBBlacks);
|
||||
|
||||
if rggb_levels.count() != 4 || rggb_blacks.count() != 4 {
|
||||
Err(RawlerError::DecoderFailed("SRW: RGGB Levels and Blacks don't have 4 elements".to_string()))
|
||||
} else {
|
||||
Ok([
|
||||
(rggb_levels.force_u32(0) as f32 - rggb_blacks.force_u32(0) as f32) / 4096.0,
|
||||
(rggb_levels.force_u32(1) as f32 - rggb_blacks.force_u32(1) as f32) / 4096.0,
|
||||
(rggb_levels.force_u32(3) as f32 - rggb_blacks.force_u32(3) as f32) / 4096.0,
|
||||
f32::NAN,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract blacklevel
|
||||
/// Ironically, the data is already black level subtracted, but the
|
||||
/// WB coeffs are not. So we can return 0 here. The black level
|
||||
/// is subtracted in the get_wb() function.
|
||||
fn get_blacklevel(&self) -> Result<[u32; 4]> {
|
||||
Ok([0, 0, 0, 0])
|
||||
/*
|
||||
let rggb_blacks = fetch_tiff_tag!(self.makernote, SrwMakernote::SrwRGGBBlacks);
|
||||
if rggb_blacks.count() != 4 {
|
||||
Err(RawlerError::General("SRW: RGGB Blacks don't have 4 elements".to_string()))
|
||||
} else {
|
||||
Ok([
|
||||
rggb_blacks.force_u16(0),
|
||||
rggb_blacks.force_u16(1),
|
||||
rggb_blacks.force_u16(2),
|
||||
rggb_blacks.force_u16(3),
|
||||
])
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
crate::tags::tiff_tag_enum!(SrwMakernote);
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum SrwMakernote {
|
||||
LensModel = 0xA003,
|
||||
SrwRGGBLevels = 0xA021,
|
||||
SrwRGGBBlacks = 0xA028,
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
use image::{DynamicImage, ImageBuffer, Rgb};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::analyze::FormatDump;
|
||||
use crate::decompressors::ljpeg::*;
|
||||
use crate::exif::Exif;
|
||||
use crate::formats::tiff::reader::TiffReader;
|
||||
use crate::formats::tiff::{Entry, GenericTiffReader, Rational, Value};
|
||||
use crate::imgop::{Dim2, Rect};
|
||||
use crate::lens::{LensDescription, LensResolver};
|
||||
use crate::packed::decode_16le;
|
||||
use crate::pixarray::PixU16;
|
||||
use crate::rawsource::RawSource;
|
||||
use crate::tags::{DngTag, ExifTag, TiffCommonTag};
|
||||
use crate::{RawImage, RawLoader, alloc_image_ok};
|
||||
use crate::{RawlerError, Result};
|
||||
|
||||
use super::{BlackLevel, CFAConfig, Camera, Decoder, FormatHint, RawDecodeParams, RawMetadata, RawPhotometricInterpretation, WhiteLevel};
|
||||
|
||||
/// 3FR format encapsulation for analyzer
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TfrFormat {
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TfrDecoder<'a> {
|
||||
camera: Camera,
|
||||
#[allow(unused)]
|
||||
rawloader: &'a RawLoader,
|
||||
tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl<'a> TfrDecoder<'a> {
|
||||
pub fn new(_file: &RawSource, tiff: GenericTiffReader, rawloader: &'a RawLoader) -> Result<TfrDecoder<'a>> {
|
||||
debug!("3FR decoder choosen");
|
||||
let camera = rawloader.check_supported(tiff.root_ifd())?;
|
||||
//let makernotes = new_makernote(file, 8).map_err(|ioerr| RawlerError::with_io_error("load 3FR makernotes", file.path(), ioerr))?;
|
||||
Ok(TfrDecoder {
|
||||
camera,
|
||||
tiff,
|
||||
rawloader,
|
||||
// makernotes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for TfrDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let raw = self
|
||||
.tiff
|
||||
.find_first_ifd_with_tag(TiffCommonTag::WhiteLevel)
|
||||
.ok_or_else(|| RawlerError::DecoderFailed(format!("Failed to find a IFD with WhilteLevel tag")))?;
|
||||
|
||||
let whitelevel = raw
|
||||
.get_entry(TiffCommonTag::WhiteLevel)
|
||||
.map(|tag| WhiteLevel::new(vec![tag.force_u16(0) as u32]));
|
||||
|
||||
let blacklevel = raw
|
||||
.get_entry(TiffCommonTag::BlackLevels)
|
||||
.map(|tag| BlackLevel::new(&[tag.force_u16(0)], 1, 1, 1));
|
||||
|
||||
let width = fetch_tiff_tag!(raw, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(raw, TiffCommonTag::ImageLength).force_usize(0);
|
||||
let offset = fetch_tiff_tag!(raw, TiffCommonTag::StripOffsets).force_usize(0);
|
||||
|
||||
let src = file.subview_until_eof(offset as u64)?;
|
||||
|
||||
let image = if self.camera.find_hint("uncompressed") {
|
||||
decode_16le(src, width, height, dummy)
|
||||
} else {
|
||||
self.decode_compressed(src, width, height, dummy)?
|
||||
};
|
||||
|
||||
let crop = Rect::from_tiff(raw).or_else(|| self.camera.crop_area.map(|area| Rect::new_with_borders(Dim2::new(width, height), &area)));
|
||||
|
||||
//crate::devtools::dump_image_u16(&image.data, width, height, "/tmp/tfrdump.pnm");
|
||||
|
||||
let cpp = 1;
|
||||
let photometric = RawPhotometricInterpretation::Cfa(CFAConfig::new_from_camera(&self.camera));
|
||||
let mut img = RawImage::new(self.camera.clone(), image, cpp, self.get_wb()?, photometric, blacklevel, whitelevel, dummy);
|
||||
|
||||
img.crop_area = crop;
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn full_image(&self, file: &RawSource, params: &RawDecodeParams) -> Result<Option<DynamicImage>> {
|
||||
if params.image_index != 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let root_ifd = &self.tiff.root_ifd();
|
||||
let buf = root_ifd
|
||||
.singlestrip_data_rawsource(file)
|
||||
.map_err(|e| RawlerError::DecoderFailed(format!("Failed to get strip data: {}", e)))?;
|
||||
let compression = root_ifd.get_entry(TiffCommonTag::Compression).ok_or("Missing tag")?.force_usize(0);
|
||||
let width = fetch_tiff_tag!(root_ifd, TiffCommonTag::ImageWidth).force_usize(0);
|
||||
let height = fetch_tiff_tag!(root_ifd, TiffCommonTag::ImageLength).force_usize(0);
|
||||
if compression == 1 {
|
||||
Ok(Some(DynamicImage::ImageRgb8(
|
||||
ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(width as u32, height as u32, buf.to_vec()).unwrap(),
|
||||
)))
|
||||
} else {
|
||||
let img = image::load_from_memory_with_format(buf, image::ImageFormat::Jpeg)
|
||||
.map_err(|err| RawlerError::DecoderFailed(format!("Failed to read JPEG: {:?}", err)))?;
|
||||
Ok(Some(img))
|
||||
}
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
FormatDump::Tfr(TfrFormat { tiff: self.tiff.clone() })
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::TFR
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
let exif = Exif::new(self.tiff.root_ifd())?;
|
||||
let mut mdata = RawMetadata::new_with_lens(&self.camera, exif, self.get_lens_description()?.cloned());
|
||||
// Read Unique ID
|
||||
if let Some(Entry {
|
||||
value: Value::Byte(unique_id), ..
|
||||
}) = self.tiff.root_ifd().get_entry(DngTag::RawDataUniqueID)
|
||||
{
|
||||
if let Ok(id) = unique_id.as_slice().try_into() {
|
||||
mdata.unique_image_id = Some(u128::from_le_bytes(id));
|
||||
}
|
||||
}
|
||||
Ok(mdata)
|
||||
}
|
||||
|
||||
fn xpacket(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<Option<Vec<u8>>> {
|
||||
match self.tiff.root_ifd().get_entry(TiffCommonTag::Xmp) {
|
||||
Some(Entry { value: Value::Byte(buf), .. }) => Ok(Some(buf.clone())),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TfrDecoder<'a> {
|
||||
/// Get lens description by analyzing TIFF tags and makernotes
|
||||
fn get_lens_description(&self) -> Result<Option<&'static LensDescription>> {
|
||||
if let Some(exif) = self.tiff.root_ifd().get_sub_ifd(TiffCommonTag::ExifIFDPointer) {
|
||||
let lens_make = exif.get_entry(ExifTag::LensMake).and_then(|entry| entry.as_string());
|
||||
let lens_model = exif.get_entry(ExifTag::LensModel).and_then(|entry| entry.as_string());
|
||||
let focal_len = match exif.get_entry(ExifTag::FocalLength) {
|
||||
Some(Entry { value: Value::Rational(x), .. }) => x.get(0).cloned(),
|
||||
Some(Entry { value: Value::Short(x), .. }) => x.get(0).copied().map(Rational::from),
|
||||
_ => None,
|
||||
};
|
||||
let resolver = LensResolver::new()
|
||||
.with_camera(&self.camera)
|
||||
.with_lens_make(lens_make)
|
||||
.with_lens_model(lens_model)
|
||||
.with_focal_len(focal_len)
|
||||
.with_mounts(&["x-mount".into()]);
|
||||
return Ok(resolver.resolve());
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
let levels = fetch_tiff_tag!(self.tiff, TiffCommonTag::AsShotNeutral);
|
||||
assert_eq!(levels.count(), 3);
|
||||
Ok([1.0 / levels.force_f32(0), 1.0 / levels.force_f32(1), 1.0 / levels.force_f32(2), f32::NAN])
|
||||
}
|
||||
|
||||
fn decode_compressed(&self, src: &[u8], width: usize, height: usize, dummy: bool) -> Result<PixU16> {
|
||||
let mut out = alloc_image_ok!(width, height, dummy);
|
||||
let decompressor = LjpegDecompressor::new_full(src, true, false)?;
|
||||
decompressor.decode(out.pixels_mut(), 0, width, width, height, dummy)?;
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
use crate::bits::Endian;
|
||||
use crate::bits::*;
|
||||
use crate::buffer::PaddedBuf;
|
||||
use crate::decoders::*;
|
||||
use crate::decompressors::ljpeg::LjpegDecompressor;
|
||||
use crate::packed::*;
|
||||
|
||||
pub fn decode_unwrapped(file: &RawSource) -> Result<RawImageData> {
|
||||
let buffer = file.subview_until_eof_padded(0)?;
|
||||
|
||||
let decoder = LEu16(&buffer, 0);
|
||||
let width = LEu16(&buffer, 2) as usize;
|
||||
let height = LEu16(&buffer, 4) as usize;
|
||||
let data = &buffer[6..];
|
||||
|
||||
if width > 64 || height > 64 {
|
||||
panic!("Trying an image larger than 64x64");
|
||||
}
|
||||
|
||||
match decoder {
|
||||
0 => {
|
||||
let table = {
|
||||
let mut t: [u16; 256] = [0; 256];
|
||||
for i in 0..256 {
|
||||
t[i] = LEu16(data, i * 2);
|
||||
}
|
||||
LookupTable::new(&t)
|
||||
};
|
||||
let data = &data[512..];
|
||||
Ok(RawImageData::Integer(decode_8bit_wtable(data, &table, width, height, false).into_inner()))
|
||||
}
|
||||
1 => Ok(RawImageData::Integer(decode_10le_lsb16(data, width, height, false).into_inner())),
|
||||
2 => Ok(RawImageData::Integer(decode_10be(data, width, height, false).into_inner())),
|
||||
3 => Ok(RawImageData::Integer(decode_12be(data, width, height, false).into_inner())),
|
||||
4 => Ok(RawImageData::Integer(decode_12be_msb16(data, width, height, false).into_inner())),
|
||||
5 => Ok(RawImageData::Integer(decode_12le_16bitaligned(data, width, height, false).into_inner())),
|
||||
6 => Ok(RawImageData::Integer(decode_12be_msb32(data, width, height, false).into_inner())),
|
||||
7 => Ok(RawImageData::Integer(decode_12le_wcontrol(data, width, height, false).into_inner())),
|
||||
8 => Ok(RawImageData::Integer(decode_12be_wcontrol(data, width, height, false).into_inner())),
|
||||
9 => Ok(RawImageData::Integer(decode_12be_interlaced(data, width, height, false).into_inner())),
|
||||
10 => Ok(RawImageData::Integer(decode_12be_interlaced_unaligned(data, width, height, false).into_inner())),
|
||||
11 => Ok(RawImageData::Integer(decode_12le(data, width, height, false).into_inner())),
|
||||
12 => Ok(RawImageData::Integer(decode_12le_unpacked(data, width, height, false).into_inner())),
|
||||
13 => Ok(RawImageData::Integer(decode_12be_unpacked(data, width, height, false).into_inner())),
|
||||
14 => Ok(RawImageData::Integer(
|
||||
decode_12be_unpacked_left_aligned(data, width, height, false).into_inner(),
|
||||
)),
|
||||
15 => Ok(RawImageData::Integer(
|
||||
decode_12le_unpacked_left_aligned(data, width, height, false).into_inner(),
|
||||
)),
|
||||
16 => Ok(RawImageData::Integer(decode_14le_unpacked(data, width, height, false).into_inner())),
|
||||
17 => Ok(RawImageData::Integer(decode_14be_unpacked(data, width, height, false).into_inner())),
|
||||
18 => Ok(RawImageData::Integer(decode_16le(data, width, height, false).into_inner())),
|
||||
19 => Ok(RawImageData::Integer(decode_16le_skiplines(data, width, height, false).into_inner())),
|
||||
20 => Ok(RawImageData::Integer(decode_16be(data, width, height, false).into_inner())),
|
||||
21 => Ok(RawImageData::Integer(arw::ArwDecoder::decode_arw1(data, width, height, false).into_inner())),
|
||||
22 => {
|
||||
let mut curve: [usize; 6] = [0, 0, 0, 0, 0, 4095];
|
||||
for i in 0..4 {
|
||||
curve[i + 1] = (LEu16(data, i * 2) & 0xfff) as usize;
|
||||
}
|
||||
|
||||
let curve = arw::ArwDecoder::calculate_curve(curve);
|
||||
let data = &data[8..];
|
||||
Ok(RawImageData::Integer(
|
||||
arw::ArwDecoder::decode_arw2(data, width, height, &curve, false).into_inner(),
|
||||
))
|
||||
}
|
||||
23 => {
|
||||
let key = LEu32(data, 0);
|
||||
let length = LEu16(data, 4) as usize;
|
||||
let data = &data[10..];
|
||||
|
||||
if length > 5000 {
|
||||
panic!("Trying an SRF style image that's too big");
|
||||
}
|
||||
|
||||
let image_data = arw::ArwDecoder::sony_decrypt(data, 0, length, key)?;
|
||||
Ok(RawImageData::Integer(decode_16be(&image_data, width, height, false).into_inner()))
|
||||
}
|
||||
24 => Ok(RawImageData::Integer(
|
||||
orf::OrfDecoder::decode_compressed(&buffer, width, height, 12, false).into_inner(),
|
||||
)),
|
||||
25 => {
|
||||
let loffsets = data;
|
||||
let data = &data[height * 4..];
|
||||
Ok(RawImageData::Integer(
|
||||
srw::SrwDecoder::decode_srw1(data, loffsets, width, height, false).into_inner(),
|
||||
))
|
||||
}
|
||||
26 => Ok(RawImageData::Integer(srw::SrwDecoder::decode_srw2(data, width, height, false).into_inner())),
|
||||
27 => Ok(RawImageData::Integer(srw::SrwDecoder::decode_srw3(data, width, height, false).into_inner())),
|
||||
28 => Ok(RawImageData::Integer(kdc::KdcDecoder::decode_dc120(data, width, height, false).into_inner())),
|
||||
29 => Ok(RawImageData::Integer(
|
||||
rw2::v4decompressor::decode_panasonic_v4(data, width, height, false, false).into_inner(),
|
||||
)),
|
||||
30 => Ok(RawImageData::Integer(
|
||||
rw2::v4decompressor::decode_panasonic_v4(data, width, height, true, false).into_inner(),
|
||||
)),
|
||||
31 => {
|
||||
let table = {
|
||||
let mut t = [0u16; 1024];
|
||||
for i in 0..1024 {
|
||||
t[i] = LEu16(data, i * 2);
|
||||
}
|
||||
LookupTable::new(&t)
|
||||
};
|
||||
let data = &data[2048..];
|
||||
Ok(RawImageData::Integer(
|
||||
dcr::DcrDecoder::decode_kodak65000(data, &table, width, height, false).into_inner(),
|
||||
))
|
||||
}
|
||||
32 => decode_ljpeg(data, width, height, false, false),
|
||||
33 => decode_ljpeg(data, width, height, false, true),
|
||||
34 => decode_ljpeg(data, width, height, true, false),
|
||||
35 => decode_ljpeg(data, width, height, true, true),
|
||||
36 => Ok(RawImageData::Integer(
|
||||
pef::PefDecoder::do_decode(data, None, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
37 => {
|
||||
let huff = data;
|
||||
let data = &data[64..];
|
||||
Ok(RawImageData::Integer(
|
||||
pef::PefDecoder::do_decode(data, Some((huff, Endian::Little)), width, height, false)
|
||||
.unwrap()
|
||||
.into_inner(),
|
||||
))
|
||||
}
|
||||
38 => {
|
||||
let huff = data;
|
||||
let data = &data[64..];
|
||||
Ok(RawImageData::Integer(
|
||||
pef::PefDecoder::do_decode(data, Some((huff, Endian::Big)), width, height, false)
|
||||
.unwrap()
|
||||
.into_inner(),
|
||||
))
|
||||
}
|
||||
39 => Ok(RawImageData::Integer(
|
||||
crw::CrwDecoder::do_decode(file, false, 0, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
40 => Ok(RawImageData::Integer(
|
||||
crw::CrwDecoder::do_decode(file, false, 1, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
41 => Ok(RawImageData::Integer(
|
||||
crw::CrwDecoder::do_decode(file, false, 2, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
42 => Ok(RawImageData::Integer(
|
||||
crw::CrwDecoder::do_decode(file, true, 0, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
43 => Ok(RawImageData::Integer(
|
||||
crw::CrwDecoder::do_decode(file, true, 1, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
44 => Ok(RawImageData::Integer(
|
||||
crw::CrwDecoder::do_decode(file, true, 2, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
45 => Ok(RawImageData::Integer(
|
||||
mos::MosDecoder::do_decode(data, false, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
46 => Ok(RawImageData::Integer(
|
||||
mos::MosDecoder::do_decode(data, true, width, height, false).unwrap().into_inner(),
|
||||
)),
|
||||
//47 => Ok(RawImageData::Integer(iiq::IiqDecoder::decode_compressed(data, height*4, 0, width, height, false).into_inner())),
|
||||
48 => decode_nef(data, width, height, Endian::Little, 12),
|
||||
49 => decode_nef(data, width, height, Endian::Little, 14),
|
||||
50 => decode_nef(data, width, height, Endian::Big, 12),
|
||||
51 => decode_nef(data, width, height, Endian::Big, 14),
|
||||
52 => {
|
||||
let coeffs = [LEf32(data, 0), LEf32(data, 4), LEf32(data, 8), LEf32(data, 12)];
|
||||
let data = PaddedBuf::new_owned(data[16..].to_vec(), data.len() - 16);
|
||||
Ok(RawImageData::Integer(
|
||||
nef::NefDecoder::decode_snef_compressed(&data, coeffs, width, height, false).into_inner(),
|
||||
))
|
||||
}
|
||||
_ => Err("No such decoder".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_ljpeg(src: &[u8], width: usize, height: usize, dng_bug: bool, csfix: bool) -> Result<RawImageData> {
|
||||
let mut out = vec![0u16; width * height];
|
||||
let decompressor = LjpegDecompressor::new_full(src, dng_bug, csfix)?;
|
||||
decompressor.decode(&mut out, 0, width, width, height, false)?;
|
||||
Ok(RawImageData::Integer(out))
|
||||
}
|
||||
|
||||
fn decode_nef(data: &[u8], width: usize, height: usize, endian: Endian, bps: usize) -> Result<RawImageData> {
|
||||
let meta = data;
|
||||
let data = &data[4096..];
|
||||
Ok(RawImageData::Integer(
|
||||
nef::NefDecoder::do_decode(data, meta, endian, width, height, bps, false).unwrap().into_inner(),
|
||||
))
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::bits::*;
|
||||
use crate::decoders::*;
|
||||
|
||||
pub fn is_x3f(file: &RawSource) -> bool {
|
||||
match file.subview(0, 4) {
|
||||
Ok(buf) => buf[0..4] == b"FOVb"[..],
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct X3fFile {
|
||||
#[allow(dead_code)]
|
||||
dirs: Vec<X3fDirectory>,
|
||||
images: Vec<X3fImage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct X3fDirectory {
|
||||
offset: usize,
|
||||
#[allow(dead_code)]
|
||||
len: usize,
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct X3fImage {
|
||||
typ: usize,
|
||||
format: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
#[allow(dead_code)]
|
||||
pitch: usize,
|
||||
doffset: usize,
|
||||
}
|
||||
|
||||
impl X3fFile {
|
||||
fn new(file: &RawSource) -> Result<X3fFile> {
|
||||
let buf = file.as_vec()?;
|
||||
let offset = LEu32(&buf, buf.len() - 4) as usize;
|
||||
let data = &buf[offset..];
|
||||
let version = LEu32(data, 4);
|
||||
if version < 0x00020000 {
|
||||
return Err(format_args!("X3F: Directory version too old {}", version).into());
|
||||
}
|
||||
let entries = LEu32(data, 8) as usize;
|
||||
let mut dirs = Vec::new();
|
||||
let mut images = Vec::new();
|
||||
for i in 0..entries {
|
||||
let dir = X3fDirectory::new(data, 12 + i * 12)?;
|
||||
if dir.id == "IMA2" {
|
||||
let img = X3fImage::new(&buf, dir.offset)?;
|
||||
images.push(img);
|
||||
}
|
||||
dirs.push(dir);
|
||||
}
|
||||
|
||||
Ok(X3fFile { dirs, images })
|
||||
}
|
||||
}
|
||||
|
||||
impl X3fDirectory {
|
||||
fn new(buf: &[u8], offset: usize) -> Result<X3fDirectory> {
|
||||
let data = &buf[offset..];
|
||||
let off = LEu32(data, 0) as usize;
|
||||
let len = LEu32(data, 4) as usize;
|
||||
let name = String::from_utf8_lossy(&data[8..12]).to_string();
|
||||
|
||||
Ok(X3fDirectory { offset: off, len, id: name })
|
||||
}
|
||||
}
|
||||
|
||||
impl X3fImage {
|
||||
fn new(buf: &[u8], offset: usize) -> Result<X3fImage> {
|
||||
let data = &buf[offset..];
|
||||
|
||||
Ok(X3fImage {
|
||||
typ: LEu32(data, 8) as usize,
|
||||
format: LEu32(data, 12) as usize,
|
||||
width: LEu32(data, 16) as usize,
|
||||
height: LEu32(data, 20) as usize,
|
||||
pitch: LEu32(data, 24) as usize,
|
||||
doffset: offset + 28,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct X3fDecoder<'a> {
|
||||
rawloader: &'a RawLoader,
|
||||
dir: X3fFile,
|
||||
}
|
||||
|
||||
impl<'a> X3fDecoder<'a> {
|
||||
pub fn new(file: &RawSource, rawloader: &'a RawLoader) -> Result<X3fDecoder<'a>> {
|
||||
let dir = X3fFile::new(file)?;
|
||||
|
||||
Ok(X3fDecoder { rawloader, dir })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder for X3fDecoder<'a> {
|
||||
fn raw_image(&self, file: &RawSource, _params: &RawDecodeParams, dummy: bool) -> Result<RawImage> {
|
||||
let buffer = file.as_vec()?;
|
||||
let caminfo = self
|
||||
.dir
|
||||
.images
|
||||
.iter()
|
||||
.find(|i| i.typ == 2 && i.format == 0x12)
|
||||
.ok_or("X3F: Couldn't find camera info")?;
|
||||
let data = &buffer[caminfo.doffset + 6..];
|
||||
if data[0..4] != b"Exif"[..] {
|
||||
return Err("X3F: Couldn't find EXIF info".into());
|
||||
}
|
||||
let tiff = IFD::new(&mut Cursor::new(&buffer), (caminfo.doffset + 12) as u32, 0, 0, Endian::Little, &[]).unwrap();
|
||||
|
||||
let camera = self.rawloader.check_supported(&tiff)?;
|
||||
|
||||
let imginfo = self.dir.images.iter().find(|i| i.typ == 1 || i.typ == 3).ok_or("X3F: Couldn't find image")?;
|
||||
let width = imginfo.width;
|
||||
let height = imginfo.height;
|
||||
let offset = imginfo.doffset;
|
||||
let src = &buffer[offset..];
|
||||
|
||||
let image = match imginfo.format {
|
||||
35 => self.decode_compressed(src, width, height, dummy)?,
|
||||
x => return Err(format_args!("X3F Don't know how to decode format {}", x).into()),
|
||||
};
|
||||
|
||||
let cpp = 3;
|
||||
let photometric = RawPhotometricInterpretation::LinearRaw;
|
||||
Ok(RawImage::new(camera, image, cpp, self.get_wb()?, photometric, None, None, dummy))
|
||||
}
|
||||
|
||||
fn format_dump(&self) -> FormatDump {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn format_hint(&self) -> FormatHint {
|
||||
FormatHint::X3F
|
||||
}
|
||||
|
||||
fn raw_metadata(&self, _file: &RawSource, _params: &RawDecodeParams) -> Result<RawMetadata> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> X3fDecoder<'a> {
|
||||
fn get_wb(&self) -> Result<[f32; 4]> {
|
||||
Ok([f32::NAN, f32::NAN, f32::NAN, f32::NAN])
|
||||
}
|
||||
|
||||
fn decode_compressed(&self, _buf: &[u8], _width: usize, _height: usize, _dummy: bool) -> Result<PixU16> {
|
||||
Err("X3F decoding not implemented yet".into())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user