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:
+277
@@ -0,0 +1,277 @@
|
||||
use std::{fs::metadata, io::Write, path::Path};
|
||||
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use hex::FromHex;
|
||||
use image::DynamicImage;
|
||||
use itertools::Itertools;
|
||||
use md5::Digest;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use crate::{
|
||||
RawImage, RawImageData, RawlerError, Result,
|
||||
decoders::{RawDecodeParams, RawMetadata, cr2::Cr2Format, cr3::Cr3Format, dng::DngFormat, iiq::IiqFormat, nef::NefFormat, pef::PefFormat, tfr::TfrFormat},
|
||||
formats::tiff::Rational,
|
||||
formats::tiff::SRational,
|
||||
imgop::{Rect, develop::RawDevelop},
|
||||
rawimage::{BlackLevel, WhiteLevel},
|
||||
rawsource::RawSource,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Md5Digest {
|
||||
digest: md5::Digest,
|
||||
}
|
||||
|
||||
impl From<md5::Digest> for Md5Digest {
|
||||
fn from(digest: md5::Digest) -> Self {
|
||||
Self { digest }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Md5Digest {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s = format!("{:x}", self.digest);
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Md5Digest {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Md5Digest, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s.len() != 32 {
|
||||
Err(D::Error::custom(format!("Invalid digest value: {}", s)))
|
||||
} else {
|
||||
Ok(Md5Digest {
|
||||
digest: Digest(<[u8; 16]>::from_hex(s).map_err(D::Error::custom)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileMetadata {
|
||||
file_size: u64,
|
||||
file_name: String,
|
||||
digest: Option<Md5Digest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AnalyzerMetadata {
|
||||
pub raw_params: RawParams,
|
||||
pub raw_metadata: RawMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum AnalyzerData {
|
||||
FileStructure(FormatDump),
|
||||
Metadata(AnalyzerMetadata),
|
||||
RawParams(RawParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AnalyzerResult {
|
||||
pub file: FileMetadata,
|
||||
pub data: Option<AnalyzerData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CaptureInfo {
|
||||
pub make: String,
|
||||
pub model: String,
|
||||
pub exposure_time: Option<Rational>,
|
||||
pub shutter_speed: Option<SRational>,
|
||||
pub exposure_bias: Option<SRational>,
|
||||
pub lens_make: Option<String>,
|
||||
pub lens_model: Option<String>,
|
||||
pub lens_spec: Option<[Rational; 4]>,
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RawParams {
|
||||
pub raw_width: usize,
|
||||
pub raw_height: usize,
|
||||
pub bit_depth: usize,
|
||||
pub crop_area: Option<Rect>,
|
||||
pub active_area: Option<Rect>,
|
||||
pub blacklevels: BlackLevel,
|
||||
pub whitelevels: WhiteLevel,
|
||||
pub wb_coeffs: (Option<f32>, Option<f32>, Option<f32>, Option<f32>),
|
||||
}
|
||||
|
||||
impl From<&RawImage> for RawParams {
|
||||
fn from(rawimage: &RawImage) -> Self {
|
||||
Self {
|
||||
raw_width: rawimage.width,
|
||||
raw_height: rawimage.height,
|
||||
bit_depth: rawimage.bps,
|
||||
crop_area: rawimage.crop_area,
|
||||
active_area: rawimage.active_area,
|
||||
blacklevels: rawimage.blacklevel.clone(),
|
||||
whitelevels: rawimage.whitelevel.clone(),
|
||||
wb_coeffs: rawimage
|
||||
.wb_coeffs
|
||||
.iter()
|
||||
.map(|c| if c.is_nan() { None } else { Some(*c) })
|
||||
.collect_tuple()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum FormatDump {
|
||||
Cr3(Cr3Format),
|
||||
Cr2(Cr2Format),
|
||||
Pef(PefFormat),
|
||||
Iiq(IiqFormat),
|
||||
Tfr(TfrFormat),
|
||||
Nef(NefFormat),
|
||||
Dng(DngFormat),
|
||||
}
|
||||
|
||||
fn file_metadata<P: AsRef<Path>>(path: P, rawfile: &RawSource) -> Result<FileMetadata> {
|
||||
let fs_meta = metadata(&path).map_err(|e| RawlerError::with_io_error("read metadata", &path, e))?;
|
||||
let digest = rawfile.digest();
|
||||
Ok(FileMetadata {
|
||||
file_name: path.as_ref().file_name().unwrap().to_string_lossy().to_string(),
|
||||
file_size: fs_meta.len(),
|
||||
digest: Some(digest.into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn analyze_metadata<P: AsRef<Path>>(path: P) -> Result<AnalyzerResult> {
|
||||
//let input = BufReader::new(File::open(&path).map_err(|e| RawlerError::with_io_error("load into buffer", &path, e))?);
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
let rawimage = decoder.raw_image(&rawfile, &RawDecodeParams::default(), true)?;
|
||||
|
||||
let mut result = AnalyzerResult {
|
||||
file: file_metadata(path, &rawfile)?,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let md = decoder.raw_metadata(&rawfile, &RawDecodeParams::default())?;
|
||||
result.data = Some(AnalyzerData::Metadata(AnalyzerMetadata {
|
||||
raw_params: RawParams::from(&rawimage),
|
||||
raw_metadata: md,
|
||||
}));
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn analyze_file_structure<P: AsRef<Path>>(path: P) -> Result<AnalyzerResult> {
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
|
||||
let result = AnalyzerResult {
|
||||
file: file_metadata(path, &rawfile)?,
|
||||
data: Some(AnalyzerData::FileStructure(decoder.format_dump())),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn extract_raw_pixels<P: AsRef<Path>>(path: P, params: &RawDecodeParams) -> Result<RawImage> {
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
let rawimage = decoder.raw_image(&rawfile, params, false)?;
|
||||
Ok(rawimage)
|
||||
}
|
||||
|
||||
pub fn raw_pixels_digest<P: AsRef<Path>>(path: P, params: &RawDecodeParams) -> Result<[u8; 16]> {
|
||||
let rawimage = extract_raw_pixels(path, params)?;
|
||||
let byte_buf = match &rawimage.data {
|
||||
RawImageData::Integer(items) => items.as_slice().as_bytes(),
|
||||
RawImageData::Float(items) => items.as_slice().as_bytes(),
|
||||
};
|
||||
Ok(md5::compute(byte_buf).into())
|
||||
}
|
||||
|
||||
pub fn extract_full_pixels<P: AsRef<Path>>(path: P, params: &RawDecodeParams) -> Result<DynamicImage> {
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
match decoder.full_image(&rawfile, params)? {
|
||||
Some(preview) => Ok(preview),
|
||||
None => Err("Unable to extract full image from RAW".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_preview_pixels<P: AsRef<Path>>(path: P, params: &RawDecodeParams) -> Result<DynamicImage> {
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
match decoder.preview_image(&rawfile, params)? {
|
||||
Some(preview) => Ok(preview),
|
||||
None => match decoder.full_image(&rawfile, params)? {
|
||||
Some(preview) => Ok(preview),
|
||||
None => Err("Unable to extract preview image from RAW".into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_thumbnail_pixels<P: AsRef<Path>>(path: P, params: &RawDecodeParams) -> Result<DynamicImage> {
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
match decoder.thumbnail_image(&rawfile, params)? {
|
||||
Some(thumbnail) => Ok(thumbnail),
|
||||
None => match decoder.preview_image(&rawfile, params)? {
|
||||
Some(thumbnail) => Ok(thumbnail),
|
||||
None => match decoder.full_image(&rawfile, params)? {
|
||||
Some(thumbnail) => Ok(thumbnail),
|
||||
None => Err("Unable to extract thumbnail image from RAW".into()),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_to_srgb<P: AsRef<Path>>(path: P, params: &RawDecodeParams) -> Result<DynamicImage> {
|
||||
let rawfile = RawSource::new(path.as_ref())?;
|
||||
// Get decoder or return
|
||||
let decoder = crate::get_decoder(&rawfile)?;
|
||||
//decoder.decode_metadata(&mut rawfile)?;
|
||||
let rawimage = decoder.raw_image(&rawfile, params, false)?;
|
||||
let dev = RawDevelop::default();
|
||||
assert_eq!(rawimage.cpp, 1);
|
||||
Ok(dev.develop_intermediate(&rawimage)?.to_dynamic_image().unwrap())
|
||||
}
|
||||
|
||||
/// Dump raw pixel data as PGM
|
||||
pub fn raw_as_pgm(width: usize, height: usize, buf: &[u16], writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
let header = format!("P5 {} {} {}\n", width, height, 65535);
|
||||
writer.write_all(header.as_bytes())?;
|
||||
for px in buf {
|
||||
writer.write_u16::<BigEndian>(*px)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dump pixel data as PGM
|
||||
pub fn rgb8_as_ppm8(width: usize, height: usize, buf: &[u8], writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
let header = format!("P6 {} {} {}\n", width, height, u8::MAX);
|
||||
writer.write_all(header.as_bytes())?;
|
||||
writer.write_all(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dump raw pixel data as PPM
|
||||
pub fn raw_as_ppm16(width: usize, height: usize, buf: &[u16], writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
let header = format!("P6 {} {} {}\n", width, height, 65535);
|
||||
writer.write_all(header.as_bytes())?;
|
||||
for px in buf {
|
||||
writer.write_u16::<BigEndian>(*px)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user