// 回写:把 IR 重新编码成一段合法的 TIFF 字节块。
//
// 与"把整段 APP1 换掉"的区别在于逐条目重发:本库不认识的 tag
// (包括厂商私有 tag)会原样带上,只重算偏移。有意丢弃的只有两样:
// 缩略图目录链,以及指向旧布局的缩略图指针 tag。
///|
priv struct Enc {
tag : Int
type_code : Int
count : Int
bytes : Array[Byte]
/// >= 0 时这是一个指向第 `ptr` 个子目录的指针,偏移在布局定下后回填。
ptr : Int
}
///|
priv struct Planned {
enc : Enc
/// >= 0 时值放在外部值区的这个偏移;-1 表示 4 字节内联。
ext : Int
}
///|
/// 重新编码。字节序沿用 `block.little`;tag 按升序写入(TIFF 要求)。
pub fn encode_tiff(block : TiffBlock) -> Bytes raise MetaError {
let little = block.little
let dir0 = entries_of(block.ifd0, true, little)
let dire = entries_of(block.exif, false, little)
let dirg = entries_of(block.gps, false, little)
let diri = entries_of(block.interop, false, little)
let has_g = dirg.length() > 0
let has_i = diri.length() > 0
// 只有 Interop 没有别的 Exif 条目时也得造一张 Exif 表:0xA005 挂在那里。
let has_e = dire.length() > 0 || has_i
if has_e {
dir0.push(ptr_entry(tag_exif_ifd, 0))
}
if has_g {
dir0.push(ptr_entry(tag_gps_ifd, 1))
}
if has_i {
dire.push(ptr_entry(tag_interop_ifd, 2))
}
dir0.sort_by(by_tag)
dire.sort_by(by_tag)
dirg.sort_by(by_tag)
diri.sort_by(by_tag)
let size0 = 2 + 12 * dir0.length() + 4
let size_e = 2 + 12 * dire.length() + 4
let size_g = 2 + 12 * dirg.length() + 4
let size_i = 2 + 12 * diri.length() + 4
let offe = 8 + size0
let offg = offe + (if has_e { size_e } else { 0 })
let offi = offg + (if has_g { size_g } else { 0 })
let vstart = offi + (if has_i { size_i } else { 0 })
let (p0, cur) = plan_dir(dir0, vstart)
let (pe, cur2) = plan_dir(dire, cur)
let (pg, cur3) = plan_dir(dirg, cur2)
let (pi, _) = plan_dir(diri, cur3)
let out : Array[Byte] = []
if little {
out.push(b'I')
out.push(b'I')
} else {
out.push(b'M')
out.push(b'M')
}
put_u16(out, 42, little)
put_u32(out, 8L, little)
let sub_offs = [offe, offg, offi]
emit_dir(out, p0, sub_offs, little)
if has_e {
emit_dir(out, pe, sub_offs, little)
}
if has_g {
emit_dir(out, pg, sub_offs, little)
}
if has_i {
emit_dir(out, pi, sub_offs, little)
}
emit_values(out, [p0, pe, pg, pi], vstart)
Bytes::from_array(out)
}
///|
fn by_tag(a : Enc, b : Enc) -> Int {
a.tag - b.tag
}
///|
fn entries_of(
entries : Array[IfdEntry],
drop_thumb_ptrs : Bool,
little : Bool,
) -> Array[Enc] raise MetaError {
let out : Array[Enc] = []
for e in entries {
if drop_thumb_ptrs &&
(e.tag == tag_thumb_offset || e.tag == tag_thumb_length) {
// 缩略图字节不会被重发,指针留着就是悬空引用。
continue
}
out.push(enc_entry(e, little))
}
out
}
///|
fn enc_entry(e : IfdEntry, little : Bool) -> Enc raise MetaError {
let (count, bytes) = value_bytes(e.tag, e.type_code, e.value, little)
{ tag: e.tag, type_code: e.type_code, count, bytes, ptr: -1, }
}
///|
fn ptr_entry(tag : Int, which : Int) -> Enc {
{
tag,
type_code: 4,
count: 1,
bytes: [b'\x00', b'\x00', b'\x00', b'\x00'],
ptr: which,
}
}
///|
/// 给每个条目定下放不下的下标:4 字节以内内联,否则进值区(4 字节对齐)。
fn plan_dir(entries : Array[Enc], cursor_in : Int) -> (Array[Planned], Int) {
let out : Array[Planned] = []
let mut cursor = cursor_in
for e in entries {
let n = e.bytes.length()
if n <= 4 {
out.push({ enc: e, ext: -1, })
} else {
out.push({ enc: e, ext: cursor, })
cursor = cursor + (n + 3) / 4 * 4
}
}
(out, cursor)
}
///|
fn emit_dir(
out : Array[Byte],
planned : Array[Planned],
sub_offs : Array[Int],
little : Bool,
) -> Unit {
put_u16(out, planned.length(), little)
for p in planned {
put_u16(out, p.enc.tag, little)
put_u16(out, p.enc.type_code, little)
put_u32(out, p.enc.count.to_int64(), little)
if p.enc.ptr >= 0 {
put_u32(out, sub_offs[p.enc.ptr].to_int64(), little)
} else if p.ext >= 0 {
put_u32(out, p.ext.to_int64(), little)
} else {
let mut i = 0
while i < 4 {
out.push(
if i < p.enc.bytes.length() {
p.enc.bytes[i]
} else {
b'\x00'
},
)
i = i + 1
}
}
}
// 下一目录指针:缩略图链被有意丢弃,所以恒为 0。
put_u32(out, 0L, little)
}
///|
/// 值区:按目录顺序把外部值依次落位。传目录列表而不是逐个参数,
/// 是为了让"加了第四张表却忘了写它的值"这类错误不可能发生。
fn emit_values(
out : Array[Byte],
dirs : Array[Array[Planned]],
vstart : Int,
) -> Unit {
let mut written = vstart
for d in dirs {
for p in d {
written = spill(out, p, written)
}
}
}
///|
fn spill(out : Array[Byte], p : Planned, written : Int) -> Int {
if p.ext < 0 {
written
} else {
let mut w = written
while w < p.ext {
// plan_dir 按 4 字节对齐推进,这里补齐空洞。
out.push(b'\x00')
w = w + 1
}
for b in p.enc.bytes {
out.push(b)
w = w + 1
}
w
}
}
///|
/// 值的字节与组件个数。`(type_code, value)` 形状不配对就报错——
/// `IfdEntry` 是可以外部构造的,静默写坏照片是最坏的结果。
fn value_bytes(
tag : Int,
type_code : Int,
value : TagValue,
little : Bool,
) -> (Int, Array[Byte]) raise MetaError {
match (type_code, value) {
(2, Text(s)) => {
let bytes : Array[Byte] = []
for ch in s {
let cp = ch.to_int()
if cp > 255 {
raise UnencodableText(tag, cp)
}
bytes.push(cp.to_byte())
}
// 一个字节都不补:读侧已经把 count 个字节(含结尾 NUL、含填充 NUL)
// 原样交过来了,这里再补一个就会把"没有结尾 NUL"的条目撑长一字节。
// 自己构造 Text 又要符合 EXIF 惯例的话,结尾的 NUL 由构造方带上。
(bytes.length(), bytes)
}
(1 | 6 | 7, Raw(bs)) => {
let bytes : Array[Byte] = []
for b in bs {
bytes.push(b)
}
(bytes.length(), bytes)
}
(3 | 8, Shorts(vs)) => {
let bytes : Array[Byte] = []
for n in vs {
check_u16(tag, type_code, n)
put_u16(bytes, n, little)
}
(vs.length(), bytes)
}
(4 | 9, Longs(vs)) => {
let bytes : Array[Byte] = []
for n in vs {
check_u32(tag, type_code, n)
put_u32(bytes, wrap32(n, type_code), little)
}
(vs.length(), bytes)
}
(5 | 10, Rationals(rs)) => {
let bytes : Array[Byte] = []
for r in rs {
check_u32(tag, type_code, r.num)
check_u32(tag, type_code, r.den)
put_u32(bytes, wrap32(r.num, type_code), little)
put_u32(bytes, wrap32(r.den, type_code), little)
}
(rs.length(), bytes)
}
_ =>
match type_size(type_code) {
None => raise UnsupportedType(tag, type_code)
Some(_) => raise TypeMismatch(tag, type_code)
}
}
}
///|
/// 有符号类型码下的值可能为负;写进文件前绕回无符号表示。
fn wrap32(v : Int64, type_code : Int) -> Int64 {
if type_code == 9 || type_code == 10 {
if v < 0L {
v + 4294967296L
} else {
v
}
} else {
v
}
}
///|
fn check_u16(tag : Int, type_code : Int, v : Int) -> Unit raise MetaError {
let lo = if type_code == 8 { -32768 } else { 0 }
let hi = if type_code == 8 { 32767 } else { 65535 }
if v < lo || v > hi {
raise ValueOutOfRange(tag, v.to_int64())
}
}
///|
fn check_u32(tag : Int, type_code : Int, v : Int64) -> Unit raise MetaError {
let lo = if type_code == 9 || type_code == 10 { -2147483648L } else { 0L }
let hi = if type_code == 9 || type_code == 10 {
2147483647L
} else {
4294967295L
}
if v < lo || v > hi {
raise ValueOutOfRange(tag, v)
}
}
///|
fn put_u16(out : Array[Byte], v : Int, little : Bool) -> Unit {
let lo = (v & 0xff).to_byte()
let hi = ((v >> 8) & 0xff).to_byte()
if little {
out.push(lo)
out.push(hi)
} else {
out.push(hi)
out.push(lo)
}
}
///|
fn put_u32(out : Array[Byte], v : Int64, little : Bool) -> Unit {
let b0 = (v & 255L).to_byte()
let b1 = ((v >> 8) & 255L).to_byte()
let b2 = ((v >> 16) & 255L).to_byte()
let b3 = ((v >> 24) & 255L).to_byte()
if little {
out.push(b0)
out.push(b1)
out.push(b2)
out.push(b3)
} else {
out.push(b3)
out.push(b2)
out.push(b1)
out.push(b0)
}
}