#[cfg(any(feature = "rawloader", feature = "rawler"))]
extern crate std;
#[cfg(any(feature = "rawloader", feature = "rawler"))]
use alloc::vec::Vec;
#[cfg(any(feature = "rawloader", feature = "rawler"))]
use enough::Stop;
#[cfg(feature = "rawloader")]
use whereat::at;
use zenpixels::PixelBuffer;
#[cfg(feature = "rawloader")]
use zenpixels::PixelDescriptor;
pub use crate::dng_render::OutputPrimaries;
#[cfg(feature = "rawloader")]
use crate::color;
use crate::demosaic::DemosaicMethod;
#[cfg(feature = "rawloader")]
use crate::demosaic::demosaic_to_rgb_f32;
#[cfg(feature = "rawloader")]
use crate::error::IntoBufferError;
#[cfg(any(feature = "rawloader", feature = "rawler"))]
use crate::error::{RawError, Result};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum OutputMode {
#[default]
Develop,
Linear,
CameraRaw,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct RawDecodeConfig {
pub demosaic: DemosaicMethod,
pub max_pixels: u64,
pub output: OutputMode,
pub target: OutputPrimaries,
pub exposure_ev: f32,
pub apply_crop: bool,
pub apply_orientation: bool,
pub wb_override: Option<[f32; 3]>,
}
impl Default for RawDecodeConfig {
fn default() -> Self {
Self {
demosaic: DemosaicMethod::default(),
max_pixels: 200_000_000, output: OutputMode::Develop,
target: OutputPrimaries::Srgb,
exposure_ev: 0.0,
apply_crop: true,
apply_orientation: true,
wb_override: None,
}
}
}
impl RawDecodeConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_demosaic(mut self, method: DemosaicMethod) -> Self {
self.demosaic = method;
self
}
#[must_use]
pub fn with_max_pixels(mut self, max: u64) -> Self {
self.max_pixels = max;
self
}
#[must_use]
pub fn with_output(mut self, mode: OutputMode) -> Self {
self.output = mode;
self
}
#[must_use]
pub fn with_target(mut self, primaries: OutputPrimaries) -> Self {
self.target = primaries;
self
}
#[must_use]
pub fn with_exposure_ev(mut self, ev: f32) -> Self {
self.exposure_ev = ev;
self
}
#[must_use]
pub fn with_crop(mut self, apply: bool) -> Self {
self.apply_crop = apply;
self
}
#[must_use]
pub fn with_orientation(mut self, apply: bool) -> Self {
self.apply_orientation = apply;
self
}
#[must_use]
pub fn with_wb(mut self, rgb: [f32; 3]) -> Self {
self.wb_override = Some(rgb);
self
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct RawDecodeOutput {
pub pixels: PixelBuffer,
pub info: RawInfo,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum SensorLayout {
#[default]
Bayer,
XTrans,
LinearRaw,
Unknown,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct RawInfo {
pub width: u32,
pub height: u32,
pub make: alloc::string::String,
pub model: alloc::string::String,
pub sensor_width: u32,
pub sensor_height: u32,
pub cfa_pattern: alloc::string::String,
pub is_dng: bool,
pub orientation: u16,
pub bit_depth: Option<u8>,
pub wb_coeffs: [f32; 4],
pub color_matrix: [[f32; 3]; 4],
pub black_levels: [f32; 4],
pub white_levels: [f32; 4],
pub crop_rect: Option<[u32; 4]>,
pub active_area: Option<[u32; 4]>,
pub baseline_exposure: Option<f64>,
pub sensor_layout: SensorLayout,
}
#[cfg(feature = "rawloader")]
pub(crate) fn probe(data: &[u8], stop: &dyn Stop) -> Result<RawInfo> {
stop.check().map_err(|r| at!(RawError::from(r)))?;
let raw =
rawloader::decode(&mut std::io::Cursor::new(data)).map_err(|e| at!(RawError::from(e)))?;
let is_dng = is_dng_data(data);
let crop_rect = if raw.crops.iter().any(|&c| c > 0) {
Some([
raw.crops[0] as u32,
raw.crops[1] as u32,
raw.crops[2] as u32,
raw.crops[3] as u32,
])
} else {
None
};
Ok(RawInfo {
width: raw.width as u32,
height: raw.height as u32,
make: raw.clean_make.clone(),
model: raw.clean_model.clone(),
sensor_width: raw.width as u32,
sensor_height: raw.height as u32,
cfa_pattern: raw.cfa.to_string(),
is_dng,
orientation: orientation_to_u16(&raw.orientation),
bit_depth: Some(bits_from_whitelevel(raw.whitelevels[0] as u32)),
wb_coeffs: raw.wb_coeffs,
color_matrix: raw.xyz_to_cam,
black_levels: [
raw.blacklevels[0] as f32,
raw.blacklevels[1] as f32,
raw.blacklevels[2] as f32,
raw.blacklevels[3] as f32,
],
white_levels: [
raw.whitelevels[0] as f32,
raw.whitelevels[1] as f32,
raw.whitelevels[2] as f32,
raw.whitelevels[3] as f32,
],
crop_rect,
active_area: None, baseline_exposure: None, sensor_layout: if raw.cpp > 1 {
SensorLayout::LinearRaw
} else {
SensorLayout::Bayer
},
})
}
#[cfg(feature = "rawloader")]
pub(crate) fn decode(
data: &[u8],
config: &RawDecodeConfig,
stop: &dyn Stop,
) -> Result<RawDecodeOutput> {
stop.check().map_err(|r| at!(RawError::from(r)))?;
if data.len() < 64 {
return Err(at!(RawError::Decode(
"input too short to be a valid RAW file".into()
)));
}
let data_vec = data.to_vec();
let raw =
std::panic::catch_unwind(move || rawloader::decode(&mut std::io::Cursor::new(&data_vec)))
.map_err(|_| {
at!(RawError::Decode(
"rawloader panicked on malformed input".into()
))
})?
.map_err(|e| at!(RawError::from(e)))?;
let width = raw.width;
let height = raw.height;
let pixels = width as u64 * height as u64;
if pixels > config.max_pixels {
return Err(at!(RawError::LimitExceeded(alloc::format!(
"image {width}x{height} = {pixels} pixels exceeds limit of {}",
config.max_pixels
))));
}
stop.check().map_err(|r| at!(RawError::from(r)))?;
let normalized = normalize_raw_data(&raw).map_err(|e| at!(e))?;
stop.check().map_err(|r| at!(RawError::from(r)))?;
if raw.cpp > 1 {
return decode_non_bayer(raw, normalized, config, stop);
}
let mut rgb = demosaic_to_rgb_f32(&normalized, width, height, &raw.cfa, config.demosaic);
stop.check().map_err(|r| at!(RawError::from(r)))?;
if config.output != OutputMode::CameraRaw {
let wb = if let Some(override_wb) = config.wb_override {
[
override_wb[0],
override_wb[1],
override_wb[2],
override_wb[1],
]
} else {
raw.wb_coeffs
};
color::apply_color_pipeline(&mut rgb, wb, raw.xyz_to_cam);
if config.exposure_ev.abs() > 1e-6 {
let mult = 2.0f32.powf(config.exposure_ev);
for v in rgb.iter_mut() {
*v *= mult;
}
}
}
stop.check().map_err(|r| at!(RawError::from(r)))?;
let (cropped_rgb, out_w, out_h) = if config.apply_crop {
apply_crop(&rgb, width, height, &raw.crops)
} else {
(rgb, width, height)
};
stop.check().map_err(|r| at!(RawError::from(r)))?;
let is_dng = is_dng_data(data);
let raw_orient = orientation_to_u16(&raw.orientation);
let (final_rgb, final_w, final_h, final_orient) = if config.apply_orientation && raw_orient > 1
{
let (data, w, h) = crate::orient::apply_orientation(cropped_rgb, out_w, out_h, raw_orient);
(data, w, h, 1u16)
} else {
(cropped_rgb, out_w, out_h, raw_orient)
};
stop.check().map_err(|r| at!(RawError::from(r)))?;
let crop_rect = if raw.crops.iter().any(|&c| c > 0) {
Some([
raw.crops[0] as u32,
raw.crops[1] as u32,
raw.crops[2] as u32,
raw.crops[3] as u32,
])
} else {
None
};
let info = RawInfo {
width: final_w as u32,
height: final_h as u32,
make: raw.clean_make.clone(),
model: raw.clean_model.clone(),
sensor_width: raw.width as u32,
sensor_height: raw.height as u32,
cfa_pattern: raw.cfa.to_string(),
is_dng,
orientation: final_orient,
bit_depth: Some(bits_from_whitelevel(raw.whitelevels[0] as u32)),
wb_coeffs: raw.wb_coeffs,
color_matrix: raw.xyz_to_cam,
black_levels: [
raw.blacklevels[0] as f32,
raw.blacklevels[1] as f32,
raw.blacklevels[2] as f32,
raw.blacklevels[3] as f32,
],
white_levels: [
raw.whitelevels[0] as f32,
raw.whitelevels[1] as f32,
raw.whitelevels[2] as f32,
raw.whitelevels[3] as f32,
],
crop_rect,
active_area: None,
baseline_exposure: None,
sensor_layout: SensorLayout::Bayer,
};
match config.output {
OutputMode::Develop => {
let mut gamma_rgb = final_rgb;
color::apply_srgb_gamma(&mut gamma_rgb);
let u16_data = color::f32_to_u16(&gamma_rgb);
let buf = PixelBuffer::from_vec(
u16_data,
final_w as u32,
final_h as u32,
PixelDescriptor::RGB16_SRGB,
)
.map_err(|e| at!(RawError::Buffer(e.into_buffer_error())))?;
Ok(RawDecodeOutput { pixels: buf, info })
}
OutputMode::Linear | OutputMode::CameraRaw => {
let byte_data: Vec<u8> = bytemuck::cast_slice::<f32, u8>(&final_rgb).to_vec();
let buf = PixelBuffer::from_vec(
byte_data,
final_w as u32,
final_h as u32,
PixelDescriptor::RGBF32_LINEAR,
)
.map_err(|e| at!(RawError::Buffer(e.into_buffer_error())))?;
Ok(RawDecodeOutput { pixels: buf, info })
}
}
}
#[cfg(feature = "rawloader")]
fn decode_non_bayer(
raw: rawloader::RawImage,
normalized: Vec<f32>,
config: &RawDecodeConfig,
stop: &dyn Stop,
) -> Result<RawDecodeOutput> {
let width = raw.width;
let height = raw.height;
let cpp = raw.cpp;
let mut rgb = Vec::with_capacity(width * height * 3);
for i in 0..width * height {
let base = i * cpp;
rgb.push(if base < normalized.len() {
normalized[base]
} else {
0.0
});
rgb.push(if base + 1 < normalized.len() {
normalized[base + 1]
} else {
0.0
});
rgb.push(if base + 2 < normalized.len() {
normalized[base + 2]
} else {
0.0
});
}
stop.check().map_err(|r| at!(RawError::from(r)))?;
if config.output != OutputMode::CameraRaw {
let wb = if let Some(override_wb) = config.wb_override {
[
override_wb[0],
override_wb[1],
override_wb[2],
override_wb[1],
]
} else {
raw.wb_coeffs
};
color::apply_color_pipeline(&mut rgb, wb, raw.xyz_to_cam);
if config.exposure_ev.abs() > 1e-6 {
let mult = 2.0f32.powf(config.exposure_ev);
for v in rgb.iter_mut() {
*v *= mult;
}
}
}
stop.check().map_err(|r| at!(RawError::from(r)))?;
let (cropped_rgb, out_w, out_h) = if config.apply_crop {
apply_crop(&rgb, width, height, &raw.crops)
} else {
(rgb, width, height)
};
let is_dng = false;
let raw_orient = orientation_to_u16(&raw.orientation);
let (final_rgb, final_w, final_h, final_orient) = if config.apply_orientation && raw_orient > 1
{
let (data, w, h) = crate::orient::apply_orientation(cropped_rgb, out_w, out_h, raw_orient);
(data, w, h, 1u16)
} else {
(cropped_rgb, out_w, out_h, raw_orient)
};
let crop_rect = if raw.crops.iter().any(|&c| c > 0) {
Some([
raw.crops[0] as u32,
raw.crops[1] as u32,
raw.crops[2] as u32,
raw.crops[3] as u32,
])
} else {
None
};
let info = RawInfo {
width: final_w as u32,
height: final_h as u32,
make: raw.clean_make,
model: raw.clean_model,
sensor_width: raw.width as u32,
sensor_height: raw.height as u32,
cfa_pattern: raw.cfa.to_string(),
is_dng,
orientation: final_orient,
bit_depth: Some(bits_from_whitelevel(raw.whitelevels[0] as u32)),
wb_coeffs: raw.wb_coeffs,
color_matrix: raw.xyz_to_cam,
black_levels: [
raw.blacklevels[0] as f32,
raw.blacklevels[1] as f32,
raw.blacklevels[2] as f32,
raw.blacklevels[3] as f32,
],
white_levels: [
raw.whitelevels[0] as f32,
raw.whitelevels[1] as f32,
raw.whitelevels[2] as f32,
raw.whitelevels[3] as f32,
],
crop_rect,
active_area: None,
baseline_exposure: None,
sensor_layout: SensorLayout::LinearRaw,
};
match config.output {
OutputMode::Develop => {
let mut gamma_rgb = final_rgb;
color::apply_srgb_gamma(&mut gamma_rgb);
let u16_data = color::f32_to_u16(&gamma_rgb);
let buf = PixelBuffer::from_vec(
u16_data,
final_w as u32,
final_h as u32,
PixelDescriptor::RGB16_SRGB,
)
.map_err(|e| at!(RawError::Buffer(e.into_buffer_error())))?;
Ok(RawDecodeOutput { pixels: buf, info })
}
OutputMode::Linear | OutputMode::CameraRaw => {
let byte_data: Vec<u8> = bytemuck::cast_slice::<f32, u8>(&final_rgb).to_vec();
let buf = PixelBuffer::from_vec(
byte_data,
final_w as u32,
final_h as u32,
PixelDescriptor::RGBF32_LINEAR,
)
.map_err(|e| at!(RawError::Buffer(e.into_buffer_error())))?;
Ok(RawDecodeOutput { pixels: buf, info })
}
}
}
#[cfg(feature = "rawloader")]
fn normalize_raw_data(raw: &rawloader::RawImage) -> core::result::Result<Vec<f32>, RawError> {
let width = raw.width;
let height = raw.height;
let cpp = raw.cpp;
let total = width * height * cpp;
let black = raw.blacklevels;
let white = raw.whitelevels;
match &raw.data {
rawloader::RawImageData::Integer(data) => {
if data.len() < total {
return Err(RawError::InvalidInput(alloc::format!(
"expected {} pixels, got {}",
total,
data.len()
)));
}
let mut out = Vec::with_capacity(total);
for (i, &sample) in data.iter().enumerate().take(total) {
let ch = if cpp == 1 {
raw.cfa.color_at(i / width, i % width)
} else {
i % cpp
};
let bl = black[ch.min(3)] as f32;
let wl = white[ch.min(3)] as f32;
let range = (wl - bl).max(1.0);
let val = (sample as f32 - bl) / range;
out.push(val.clamp(0.0, 1.0));
}
Ok(out)
}
rawloader::RawImageData::Float(data) => {
if data.len() < total {
return Err(RawError::InvalidInput(alloc::format!(
"expected {} pixels, got {}",
total,
data.len()
)));
}
let mut out = Vec::with_capacity(total);
for (i, &sample) in data.iter().enumerate().take(total) {
let ch = if cpp == 1 {
raw.cfa.color_at(i / width, i % width)
} else {
i % cpp
};
let bl = black[ch.min(3)] as f32;
let wl = white[ch.min(3)] as f32;
let range = (wl - bl).max(1.0);
let val = (sample - bl) / range;
out.push(val.clamp(0.0, 1.0));
}
Ok(out)
}
}
}
#[cfg(feature = "rawloader")]
fn apply_crop(
rgb: &[f32],
width: usize,
height: usize,
crops: &[usize; 4],
) -> (Vec<f32>, usize, usize) {
let top = crops[0];
let right = crops[1];
let bottom = crops[2];
let left = crops[3];
if top + bottom >= height || left + right >= width {
return (rgb.to_vec(), width, height);
}
let new_w = width - left - right;
let new_h = height - top - bottom;
let mut cropped = Vec::with_capacity(new_w * new_h * 3);
for row in top..height - bottom {
let src_start = (row * width + left) * 3;
let src_end = src_start + new_w * 3;
cropped.extend_from_slice(&rgb[src_start..src_end]);
}
(cropped, new_w, new_h)
}
pub(crate) fn is_dng_data(data: &[u8]) -> bool {
if data.len() < 12 {
return false;
}
let is_tiff = (data[0] == b'I' && data[1] == b'I' && data[2] == 42 && data[3] == 0)
|| (data[0] == b'M' && data[1] == b'M' && data[2] == 0 && data[3] == 42);
if !is_tiff {
return false;
}
let search_len = data.len().min(4096);
let le = data[0] == b'I';
for i in 0..search_len.saturating_sub(1) {
if le {
if data[i] == 0x12 && data[i + 1] == 0xC6 {
return true;
}
} else if data[i] == 0xC6 && data[i + 1] == 0x12 {
return true;
}
}
false
}
#[cfg(feature = "rawloader")]
fn orientation_to_u16(orient: &rawloader::Orientation) -> u16 {
match orient {
rawloader::Orientation::Normal => 1,
rawloader::Orientation::HorizontalFlip => 2,
rawloader::Orientation::Rotate180 => 3,
rawloader::Orientation::VerticalFlip => 4,
rawloader::Orientation::Transpose => 5,
rawloader::Orientation::Rotate90 => 6,
rawloader::Orientation::Transverse => 7,
rawloader::Orientation::Rotate270 => 8,
_ => 1,
}
}
pub(crate) fn bits_from_whitelevel(wl: u32) -> u8 {
if wl == 0 {
return 16;
}
(32 - wl.leading_zeros()) as u8
}
pub(crate) fn is_raw_file(data: &[u8]) -> bool {
if data.len() < 12 {
return false;
}
let is_tiff = (data[0] == b'I' && data[1] == b'I' && data[2] == 42 && data[3] == 0)
|| (data[0] == b'M' && data[1] == b'M' && data[2] == 0 && data[3] == 42);
if is_tiff {
return true;
}
if data[0] == b'I' && data[1] == b'I' && data[2] == 0x52 && data[3] == 0x4F {
return true;
}
if data.len() >= 8 && &data[..8] == b"FUJIFILM" {
return true;
}
if data[0] == b'I' && data[1] == b'I' && data[2] == 0x55 && data[3] == 0x00 {
return true;
}
if &data[4..8] == b"ftyp" && &data[8..12] == b"crx " {
return true;
}
false
}