use crate::error::{Error, Result};
use crate::tag::{Tag, TagGroup, TagId};
use crate::value::Value;
pub fn read_raf(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 100 || !data.starts_with(b"FUJIFILMCCD-RAW") {
return Err(Error::InvalidData("not a Fujifilm RAF file".into()));
}
let mut tags = Vec::new();
let version = crate::encoding::decode_utf8_or_latin1(&data[0x3C..0x40]).to_string();
tags.push(mk("RAFVersion", "RAF Version", Value::String(version)));
let model_end = data[0x1C..0x3C]
.iter()
.position(|&b| b == 0)
.unwrap_or(0x20);
let model = crate::encoding::decode_utf8_or_latin1(&data[0x1C..0x1C + model_end]).to_string();
if !model.is_empty() {
tags.push(mk("Model", "Camera Model", Value::String(model)));
}
if data.len() >= 0x70 && data[0x6c] == 0 {
let compression = u32::from_be_bytes([data[0x6c], data[0x6d], data[0x6e], data[0x6f]]);
let comp_str = match compression {
0 => "Uncompressed",
2 => "Lossless",
3 => "Lossy",
_ => "Unknown",
};
tags.push(mk_loc(
"RAFCompression",
"RAF Compression",
Value::U32(compression),
comp_str.to_string(),
));
}
let jpeg_offset;
let jpeg_length;
if data.len() >= 0x5C {
jpeg_offset = u32::from_be_bytes([data[0x54], data[0x55], data[0x56], data[0x57]]) as usize;
jpeg_length = u32::from_be_bytes([data[0x58], data[0x59], data[0x5A], data[0x5B]]) as usize;
} else {
jpeg_offset = 0;
jpeg_length = 0;
}
if jpeg_offset > 0 && jpeg_offset + jpeg_length <= data.len() && jpeg_length > 0 {
let jpeg_data = &data[jpeg_offset..jpeg_offset + jpeg_length];
tags.push(Tag {
id: TagId::Text("PreviewImage".into()),
name: "PreviewImage".into(),
description: "Preview Image".into(),
group: TagGroup {
family0: "RAF".into(),
family1: "RAF".into(),
family2: "Preview".into(),
},
raw_value: Value::Binary(jpeg_data.to_vec()),
print_value: format!("(Binary data {} bytes)", jpeg_length),
priority: 0,
});
if jpeg_data.starts_with(&[0xFF, 0xD8, 0xFF]) {
if let Ok(jpeg_tags) = crate::formats::jpeg::read_jpeg(jpeg_data) {
tags.extend(jpeg_tags);
}
}
}
if data.len() >= 0x64 {
let dir_offset =
u32::from_be_bytes([data[0x5C], data[0x5D], data[0x5E], data[0x5F]]) as usize;
let dir_length =
u32::from_be_bytes([data[0x60], data[0x61], data[0x62], data[0x63]]) as usize;
if dir_offset > 0 && dir_offset + dir_length <= data.len() {
parse_raf_directory(&data[dir_offset..dir_offset + dir_length], &mut tags);
}
}
Ok(tags)
}
fn parse_raf_directory(data: &[u8], tags: &mut Vec<Tag>) {
if data.len() < 4 {
return;
}
let num_entries = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
if num_entries > 256 {
return; }
let mut pos = 4;
let mut fuji_layout = false;
{
let mut scan_pos = 4;
for _ in 0..num_entries {
if scan_pos + 4 > data.len() {
break;
}
let tag_id = u16::from_be_bytes([data[scan_pos], data[scan_pos + 1]]);
let data_len = u16::from_be_bytes([data[scan_pos + 2], data[scan_pos + 3]]) as usize;
scan_pos += 4;
if scan_pos + data_len > data.len() {
break;
}
if tag_id == 0x130 && data_len >= 1 {
fuji_layout = (data[scan_pos] & 0x80) != 0;
}
scan_pos += data_len;
}
}
for _ in 0..num_entries {
if pos + 4 > data.len() {
break;
}
let tag_id = u16::from_be_bytes([data[pos], data[pos + 1]]);
let data_len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if pos + data_len > data.len() {
break;
}
let val_data = &data[pos..pos + data_len];
pos += data_len;
if let Some(tag) = decode_raf_tag(tag_id, data_len, val_data, fuji_layout) {
tags.push(tag);
}
}
}
fn decode_raf_tag(tag_id: u16, data_len: usize, val_data: &[u8], fuji_layout: bool) -> Option<Tag> {
match tag_id {
0x100 if data_len >= 4 => {
let height = u16::from_be_bytes([val_data[0], val_data[1]]) as u32;
let width = u16::from_be_bytes([val_data[2], val_data[3]]) as u32;
let s = format!("{}x{}", width, height);
Some(mk_loc(
"RawImageFullSize",
"Raw Image Full Size",
Value::String(s.clone()),
s,
))
}
0x110 if data_len >= 4 => {
let top = u16::from_be_bytes([val_data[0], val_data[1]]);
let left = u16::from_be_bytes([val_data[2], val_data[3]]);
let s = format!("{} {}", top, left);
Some(mk_loc(
"RawImageCropTopLeft",
"Raw Image Crop Top Left",
Value::String(s.clone()),
s,
))
}
0x111 if data_len >= 4 => {
let height = u16::from_be_bytes([val_data[0], val_data[1]]) as u32;
let width = u16::from_be_bytes([val_data[2], val_data[3]]) as u32;
let s = format!("{}x{}", width, height);
Some(mk_loc(
"RawImageCroppedSize",
"Raw Image Cropped Size",
Value::String(s.clone()),
s,
))
}
0x121 if data_len >= 4 => {
let mut height = u16::from_be_bytes([val_data[0], val_data[1]]) as u32;
let mut width = u16::from_be_bytes([val_data[2], val_data[3]]) as u32;
if fuji_layout {
width /= 2;
height *= 2;
}
let s = format!("{}x{}", width, height);
Some(mk_loc(
"RawImageSize",
"Raw Image Size",
Value::String(s.clone()),
s,
))
}
0x130 => {
let bytes: Vec<u8> = val_data[..data_len.min(4)].to_vec();
let s = bytes
.iter()
.map(|b| b.to_string())
.collect::<Vec<_>>()
.join(" ");
Some(mk_loc(
"FujiLayout",
"Fuji Layout",
Value::String(s.clone()),
s,
))
}
0x2000 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsAuto",
"WB GRGB Levels Auto",
)),
0x2100 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsDaylight",
"WB GRGB Levels Daylight",
)),
0x2200 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsCloudy",
"WB GRGB Levels Cloudy",
)),
0x2300 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsDaylightFluor",
"WB GRGB Levels Daylight Fluor",
)),
0x2301 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsDayWhiteFluor",
"WB GRGB Levels Day White Fluor",
)),
0x2302 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsWhiteFluorescent",
"WB GRGB Levels White Fluorescent",
)),
0x2310 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsWarmWhiteFluor",
"WB GRGB Levels Warm White Fluor",
)),
0x2311 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsLivingRoomWarmWhiteFluor",
"WB GRGB Levels Living Room Warm White Fluor",
)),
0x2400 if data_len >= 8 => Some(decode_wb_grgb(
val_data,
"WB_GRGBLevelsTungsten",
"WB GRGB Levels Tungsten",
)),
0x2ff0 if data_len >= 8 => {
Some(decode_wb_grgb(val_data, "WB_GRGBLevels", "WB GRGB Levels"))
}
0x9200 if data_len >= 4 => {
let n = i16::from_be_bytes([val_data[0], val_data[1]]) as f64;
let d = i16::from_be_bytes([val_data[2], val_data[3]]) as f64;
if d != 0.0 {
let ratio = n / d;
let value = if ratio > 0.0 {
ratio.ln() / 2.0_f64.ln()
} else if ratio == 0.0 {
0.0
} else {
return None;
};
let print = if value == 0.0 {
"0".to_string()
} else {
format!("{:+.1}", value)
};
Some(mk_loc(
"RelativeExposure",
"Relative Exposure",
Value::F64(value),
print,
))
} else {
None
}
}
0x9650 if data_len >= 4 => {
let n = i16::from_be_bytes([val_data[0], val_data[1]]) as f64;
let d = i16::from_be_bytes([val_data[2], val_data[3]]) as f64;
if d != 0.0 {
let value = n / d;
let print = if value == 0.0 {
"0".to_string()
} else {
format!("{:+.1}", value)
};
Some(mk_loc(
"RawExposureBias",
"Raw Exposure Bias",
Value::F64(value),
print,
))
} else {
None
}
}
_ => None, }
}
fn decode_wb_grgb(val_data: &[u8], name: &str, description: &str) -> Tag {
let g1 = u16::from_be_bytes([val_data[0], val_data[1]]);
let r = u16::from_be_bytes([val_data[2], val_data[3]]);
let g2 = u16::from_be_bytes([val_data[4], val_data[5]]);
let b = u16::from_be_bytes([val_data[6], val_data[7]]);
let s = format!("{} {} {} {}", g1, r, g2, b);
mk_loc(name, description, Value::String(s.clone()), s)
}
fn mk(name: &str, description: &str, value: Value) -> Tag {
let pv = value.to_display_string();
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: description.to_string(),
group: TagGroup {
family0: "RAF".into(),
family1: "RAF".into(),
family2: "Camera".into(),
},
raw_value: value,
print_value: pv,
priority: 0,
}
}
fn mk_loc(name: &str, description: &str, value: Value, print: String) -> Tag {
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: description.to_string(),
group: TagGroup {
family0: "RAF".into(),
family1: "RAF".into(),
family2: "Camera".into(),
},
raw_value: value,
print_value: print,
priority: 0,
}
}