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:
+231
@@ -0,0 +1,231 @@
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
io::{Cursor, Seek, Write},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::{
|
||||
RawImage, RawlerError,
|
||||
decoders::{Decoder, RawDecodeParams, WellKnownIFD},
|
||||
dng::{DNG_VERSION_V1_4, PREVIEW_JPEG_QUALITY, original::OriginalCompressed, writer::DngWriter},
|
||||
formats::tiff::Entry,
|
||||
imgop::develop::RawDevelop,
|
||||
rawsource::RawSource,
|
||||
tags::{DngTag, ExifTag, TiffCommonTag},
|
||||
};
|
||||
|
||||
use super::{CropMode, DngCompression, DngPhotometricConversion};
|
||||
|
||||
/// Parameters for DNG conversion
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConvertParams {
|
||||
pub embedded: bool,
|
||||
pub compression: DngCompression,
|
||||
pub photometric_conversion: DngPhotometricConversion,
|
||||
pub apply_scaling: bool,
|
||||
pub crop: CropMode,
|
||||
pub predictor: u8,
|
||||
pub preview: bool,
|
||||
pub thumbnail: bool,
|
||||
pub artist: Option<String>,
|
||||
pub software: String,
|
||||
pub index: usize,
|
||||
pub keep_mtime: bool,
|
||||
}
|
||||
|
||||
impl Default for ConvertParams {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
embedded: true,
|
||||
compression: DngCompression::Lossless,
|
||||
photometric_conversion: DngPhotometricConversion::Original,
|
||||
apply_scaling: false,
|
||||
crop: CropMode::Best,
|
||||
predictor: 1,
|
||||
preview: true,
|
||||
thumbnail: true,
|
||||
artist: None,
|
||||
software: "DNGLab".into(),
|
||||
index: 0,
|
||||
keep_mtime: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a raw input file into DNG
|
||||
///
|
||||
/// We don't accept a DNG file path here, because we don't know
|
||||
/// how to handle existing target files, buffering, etc.
|
||||
/// This is up to the caller.
|
||||
pub fn convert_raw_file<W: Write + Seek + Send>(raw: &Path, dng: &mut W, params: &ConvertParams) -> crate::Result<()> {
|
||||
let original_filename = raw.file_name().and_then(OsStr::to_str).unwrap_or_default();
|
||||
//let raw_stream = BufReader::new(File::open(raw)?); // TODO: add path hint to error?
|
||||
//let rawfile = RawFile::new(PathBuf::from(raw), raw_stream);
|
||||
|
||||
let rawfile = Arc::new(RawSource::new(raw)?);
|
||||
|
||||
let original_compress_thread = if params.embedded {
|
||||
let orig_source = rawfile.clone();
|
||||
Some(std::thread::spawn(move || OriginalCompressed::compress(&mut orig_source.reader())))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
internal_convert(&rawfile, dng, original_filename, original_compress_thread, params)
|
||||
}
|
||||
|
||||
/// Convert a raw input file into DNG
|
||||
pub fn convert_raw_source<W>(raw_source: &RawSource, dng: &mut W, original_filename: impl AsRef<str>, params: &ConvertParams) -> crate::Result<()>
|
||||
where
|
||||
W: Write + Seek + Send,
|
||||
{
|
||||
let original_compress_thread = if params.embedded {
|
||||
let mut original_stream = Cursor::new(raw_source.as_vec()?);
|
||||
Some(std::thread::spawn(move || OriginalCompressed::compress(&mut original_stream)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
internal_convert(raw_source, dng, original_filename, original_compress_thread, params)
|
||||
}
|
||||
|
||||
fn internal_convert<W>(
|
||||
rawfile: &RawSource,
|
||||
dng: &mut W,
|
||||
original_filename: impl AsRef<str>,
|
||||
original_compress_thread: Option<JoinHandle<Result<OriginalCompressed, std::io::Error>>>,
|
||||
params: &ConvertParams,
|
||||
) -> crate::Result<()>
|
||||
where
|
||||
W: Write + Seek + Send,
|
||||
{
|
||||
let decoder = crate::get_decoder(rawfile)?;
|
||||
let raw_params = RawDecodeParams { image_index: params.index };
|
||||
let mut rawimage = decoder.raw_image(rawfile, &raw_params, false)?;
|
||||
let metadata = decoder.raw_metadata(rawfile, &raw_params)?;
|
||||
|
||||
log::info!(
|
||||
"DNG conversion: '{}', make: {}, model: {}, raw-image-count: {}",
|
||||
original_filename.as_ref(),
|
||||
rawimage.clean_make,
|
||||
rawimage.clean_model,
|
||||
decoder.raw_image_count()?
|
||||
);
|
||||
|
||||
if params.apply_scaling {
|
||||
rawimage.apply_scaling()?;
|
||||
}
|
||||
|
||||
log::debug!("wb coeff: {:?}", rawimage.wb_coeffs);
|
||||
|
||||
let mut dng = DngWriter::new(dng, DNG_VERSION_V1_4)?;
|
||||
|
||||
// Write RAW image for subframe type 0
|
||||
// If no thumbnail should be written to root IFD, we need to put the raw image into
|
||||
// root IFD instead.
|
||||
let mut raw = if params.thumbnail { dng.subframe(0) } else { dng.subframe_on_root(0) };
|
||||
raw.raw_image(&rawimage, params.crop, params.compression, params.photometric_conversion, params.predictor)?;
|
||||
// Check for DNG raw IFD related tags
|
||||
if let Some(dng_raw_ifd) = decoder.ifd(WellKnownIFD::VirtualDngRawTags)? {
|
||||
raw.ifd_mut().copy(dng_raw_ifd.value_iter());
|
||||
}
|
||||
raw.finalize()?;
|
||||
|
||||
// Write preview and thumbnail if requested
|
||||
if params.preview || params.thumbnail {
|
||||
match generate_preview(rawfile, decoder.as_ref(), &rawimage, &raw_params) {
|
||||
Ok(image) => {
|
||||
if params.preview {
|
||||
let mut preview = dng.subframe(1);
|
||||
preview.preview(&image, PREVIEW_JPEG_QUALITY)?;
|
||||
preview.finalize()?;
|
||||
}
|
||||
if params.thumbnail {
|
||||
dng.thumbnail(&image)?;
|
||||
}
|
||||
}
|
||||
Err(err) => log::warn!("Failed to get review image, continue anyway: {:?}", err),
|
||||
}
|
||||
}
|
||||
// Write metadata
|
||||
dng.load_base_tags(&rawimage)?;
|
||||
dng.load_metadata(&metadata)?;
|
||||
if !dng.root_ifd().contains(ExifTag::Orientation) {
|
||||
dng.root_ifd_mut().add_tag(ExifTag::Orientation, rawimage.orientation.to_u16());
|
||||
}
|
||||
|
||||
// Check for DNG root IFD related tags
|
||||
if let Some(dng_root_ifd) = decoder.ifd(WellKnownIFD::VirtualDngRootTags)? {
|
||||
dng.root_ifd_mut().copy(dng_root_ifd.value_iter());
|
||||
}
|
||||
|
||||
// Check for TIFF root IFD related tags
|
||||
if let Some(tiff_root) = decoder.ifd(WellKnownIFD::Root)? {
|
||||
dng.root_ifd_mut().copy(tiff_root.value_iter().filter(|(tag, _)| {
|
||||
[
|
||||
// Tags from CinemaDNG files
|
||||
TiffCommonTag::TimeCodes as u16,
|
||||
TiffCommonTag::FrameFrate as u16,
|
||||
TiffCommonTag::TStop as u16,
|
||||
]
|
||||
.contains(tag)
|
||||
}));
|
||||
}
|
||||
|
||||
// Remove makernotes from EXIF if MakerNoteSafety is not 1 (safe)
|
||||
if let Some(Entry {
|
||||
value: crate::formats::tiff::Value::Short(v),
|
||||
..
|
||||
}) = decoder
|
||||
.ifd(WellKnownIFD::VirtualDngRootTags)?
|
||||
.and_then(|ifd| ifd.get_entry(DngTag::MakerNoteSafety).cloned())
|
||||
{
|
||||
if v.get(0).copied().unwrap_or(0) == 0 {
|
||||
dng.exif_ifd_mut().remove_tag(ExifTag::MakerNotes);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(xpacket) = decoder.xpacket(rawfile, &raw_params)? {
|
||||
dng.xpacket(&xpacket)?;
|
||||
}
|
||||
|
||||
if let Some(handle) = original_compress_thread {
|
||||
let original = handle
|
||||
.join()
|
||||
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to join compression thread: {:?}", err)))??;
|
||||
dng.original_file(&original, original_filename)?;
|
||||
}
|
||||
|
||||
if let Some(artist) = ¶ms.artist {
|
||||
dng.root_ifd_mut().add_tag(TiffCommonTag::Artist, artist);
|
||||
}
|
||||
dng.root_ifd_mut().add_tag(TiffCommonTag::Software, ¶ms.software);
|
||||
|
||||
dng
|
||||
.root_ifd_mut()
|
||||
.add_tag(ExifTag::ModifyDate, chrono::Local::now().format("%Y:%m:%d %H:%M:%S").to_string());
|
||||
|
||||
dng.close()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_preview(rawfile: &RawSource, decoder: &dyn Decoder, rawimage: &RawImage, params: &RawDecodeParams) -> crate::Result<DynamicImage> {
|
||||
let image = match decoder.full_image(rawfile, params)? {
|
||||
Some(image) => Ok(image),
|
||||
None => {
|
||||
log::warn!("Preview image not found, try to generate sRGB from RAW");
|
||||
let dev = RawDevelop::default();
|
||||
let image = dev.develop_intermediate(rawimage)?;
|
||||
image
|
||||
.to_dynamic_image()
|
||||
.ok_or_else(|| RawlerError::DecoderFailed("Failed to generate preview image".to_string()))
|
||||
}
|
||||
}?;
|
||||
log::debug!("Using preview image with source dimension {}x{}", image.width(), image.height());
|
||||
Ok(image)
|
||||
}
|
||||
Reference in New Issue
Block a user