/// Restricted baseline TIFF decoder.
///
/// Supports little/big-endian classic TIFF (magic 42), multiple strips, tiled
/// images, PackBits/LZW compression, and a bounded BigTIFF subset. Samples are
/// chunky 8-bit grayscale/RGB/RGBA with photometric interpretation 0/1 or 2.
/// palettes and BigTIFF files requiring
/// offsets above the host Int range return `None`.
fn tiff_u16(data : Array[Byte], p : Int, little : Bool) -> Int {
if little {
data[p].to_int() | (data[p + 1].to_int() << 8)
} else {
(data[p].to_int() << 8) | data[p + 1].to_int()
}
}
fn tiff_u32(data : Array[Byte], p : Int, little : Bool) -> Int {
if little {
data[p].to_int() | (data[p + 1].to_int() << 8) |
(data[p + 2].to_int() << 16) | (data[p + 3].to_int() << 24)
} else {
(data[p].to_int() << 24) | (data[p + 1].to_int() << 16) |
(data[p + 2].to_int() << 8) | data[p + 3].to_int()
}
}
fn tiff_entry_value(
data : Array[Byte],
entry : Int,
kind : Int,
count : Int,
little : Bool,
) -> Int? {
if count != 1 { return None }
if kind == 3 { Some(tiff_u16(data, entry + 8, little)) }
else if kind == 4 { Some(tiff_u32(data, entry + 8, little)) }
else if kind == 1 { Some(data[entry + 8].to_int()) }
else { None }
}
fn tiff_range(data : Array[Byte], start : Int, length : Int) -> Bool {
start >= 0 && length >= 0 && start <= data.length() && length <= data.length() - start
}
fn tiff_values(
data : Array[Byte],
entry : Int,
kind : Int,
count : Int,
little : Bool,
) -> Array[Int]? {
if count <= 0 || (kind != 3 && kind != 4) { return None }
let size = if kind == 3 { 2 } else { 4 }
if count > data.length() / size { return None }
let total = count * size
let start = if total <= 4 { entry + 8 } else { tiff_u32(data, entry + 8, little) }
if !tiff_range(data, start, total) { return None }
let values : Array[Int] = []
for i in 0.. Array[Byte]? {
if !tiff_range(data, start, length) || limit < 0 { return None }
let out : Array[Byte] = []
let end = start + length
let mut p = start
while p < end {
let header = data[p].to_int()
p = p + 1
if header <= 127 {
let count = header + 1
if count > end - p || count > limit - out.length() { return None }
for i in 0..= 129 {
if p >= end { return None }
let value = data[p]
p = p + 1
let count = 257 - header
if count > limit - out.length() { return None }
for _ in 0.. Array[Byte]? {
if !tiff_range(data, start, length) || limit < 0 { return None }
let dictionary : Array[Array[Byte]] = Array::make(4096, [])
let mut bit_pos = 0
let mut code_bits = 9
let mut next_code = 258
let mut previous : Array[Byte]? = None
let out : Array[Byte] = []
let read_code = () => {
if bit_pos + code_bits > length * 8 { return None }
let mut value = 0
for _ in 0..> (7 - bit_pos % 8)) & 1)
bit_pos = bit_pos + 1
}
Some(value)
}
let mut finished = false
while !finished {
let code = match read_code() { Some(v) => v; None => return None }
if code == 256 {
for i in 0..<4096 { dictionary[i] = [] }
code_bits = 9; next_code = 258; previous = None
continue
}
if code == 257 { finished = true; continue }
let sequence = if code < 256 {
[code.to_byte()]
} else if code < next_code {
dictionary[code]
} else if code == next_code && previous is Some(prev) {
let candidate = prev.copy(); candidate.push(prev[0]); candidate
} else { return None }
if sequence.length() > limit - out.length() { return None }
for byte in sequence { out.push(byte) }
if previous is Some(prev) && next_code < 4096 {
let entry = prev.copy(); entry.push(sequence[0]); dictionary[next_code] = entry
next_code = next_code + 1
if next_code == (1 << code_bits) - 1 && code_bits < 12 { code_bits = code_bits + 1 }
}
previous = Some(sequence)
}
if out.length() == limit && finished { Some(out) } else { None }
}
fn tiff_write_pixel(out : Image, dst : Int, raw : Array[Byte], src : Int, samples : Int, photometric : Int) -> Unit {
if samples == 1 {
let value = raw[src]
let v = if photometric == 0 { clamp_byte(255 - value.to_int()) } else { value }
out.data[dst] = v; out.data[dst + 1] = v; out.data[dst + 2] = v; out.data[dst + 3] = b'\xFF'
} else {
out.data[dst] = raw[src]; out.data[dst + 1] = raw[src + 1]; out.data[dst + 2] = raw[src + 2]
out.data[dst + 3] = if samples == 4 { raw[src + 3] } else { b'\xFF' }
}
}
fn tiff_apply_horizontal_predictor(raw : Array[Byte], row_bytes : Int, rows : Int, samples : Int) -> Bool {
if row_bytes <= 0 || rows < 0 || samples <= 0 || row_bytes < samples { return false }
for y in 0.. Bool {
if kind != 3 || count <= 0 { return false }
if count > data.length() / 2 { return false }
let bytes = count * 2
let start = if bytes <= 4 { entry + 8 } else { tiff_u32(data, entry + 8, little) }
if !tiff_range(data, start, bytes) { return false }
for i in 0.. Image? {
if data.length() < 8 { return None }
let little = data[0] == b'I' && data[1] == b'I'
if !little && !(data[0] == b'M' && data[1] == b'M') { return None }
let magic = tiff_u16(data, 2, little)
if magic == 43 { return tiff_decode_bigtiff(data) }
if magic != 42 { return None }
let ifd = tiff_u32(data, 4, little)
if ifd < 8 || !tiff_range(data, ifd, 2) { return None }
let count = tiff_u16(data, ifd, little)
if !tiff_range(data, ifd, 2 + count * 12 + 4) { return None }
let mut width = 0
let mut height = 0
let mut compression = 1
let mut predictor = 1
let mut photometric = 2
let mut samples = 0
let mut strip_offsets : Array[Int] = []
let mut strip_byte_counts : Array[Int] = []
let mut rows_per_strip = 0
let mut tile_width = 0
let mut tile_height = 0
let mut tile_offsets : Array[Int] = []
let mut tile_byte_counts : Array[Int] = []
let mut bits_entry = -1
let mut bits_kind = 0
let mut bits_count = 0
for i in 0.. match tiff_entry_value(data, entry, kind, n, little) { Some(v) => width = v; None => () }
257 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => height = v; None => () }
258 => { bits_entry = entry; bits_kind = kind; bits_count = n }
259 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => compression = v; None => () }
262 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => photometric = v; None => () }
273 => match tiff_values(data, entry, kind, n, little) { Some(v) => strip_offsets = v; None => () }
277 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => samples = v; None => () }
278 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => rows_per_strip = v; None => () }
279 => match tiff_values(data, entry, kind, n, little) { Some(v) => strip_byte_counts = v; None => () }
317 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => predictor = v; None => () }
322 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => tile_width = v; None => () }
323 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => tile_height = v; None => () }
324 => match tiff_values(data, entry, kind, n, little) { Some(v) => tile_offsets = v; None => () }
325 => match tiff_values(data, entry, kind, n, little) { Some(v) => tile_byte_counts = v; None => () }
284 => match tiff_entry_value(data, entry, kind, n, little) { Some(v) => if v != 1 { return None }; None => () }
_ => ()
}
}
if width <= 0 || height <= 0 || (compression != 1 && compression != 5 && compression != 8 && compression != 32773) ||
(predictor != 1 && predictor != 2) || (photometric != 0 && photometric != 1 && photometric != 2) {
return None
}
if width > MAX_IMAGE_PIXELS / height { return None }
if samples == 0 { samples = if photometric == 2 { 3 } else { 1 } }
if samples != 1 && samples != 3 && samples != 4 { return None }
if (photometric == 2 && samples == 1) || (photometric != 2 && samples != 1) || bits_count != samples { return None }
if bits_entry < 0 || !tiff_bits_ok(data, bits_entry, bits_kind, bits_count, little) { return None }
let tile_mode = tile_width > 0 || tile_height > 0 || tile_offsets.length() > 0 || tile_byte_counts.length() > 0
if tile_mode {
if tile_width <= 0 || tile_height <= 0 || tile_offsets.length() == 0 || tile_offsets.length() != tile_byte_counts.length() ||
width > MAX_IMAGE_PIXELS / height || tile_width > MAX_IMAGE_PIXELS / tile_height { return None }
let tiles_x = (width + tile_width - 1) / tile_width
let tiles_y = (height + tile_height - 1) / tile_height
if tile_offsets.length() != tiles_x * tiles_y { return None }
let tile_expected = tile_width * tile_height * samples
let out = Image::new(width, height)
for tile in 0.. Some(v); _ => None }
} else { None }
let raw = match raw { Some(v) => v; None => return None }
if predictor == 2 && !tiff_apply_horizontal_predictor(raw, tile_width * samples, tile_height, samples) { return None }
let tx = tile % tiles_x; let ty = tile / tiles_x
let actual_width = if tx * tile_width + tile_width > width { width - tx * tile_width } else { tile_width }
let actual_height = if ty * tile_height + tile_height > height { height - ty * tile_height } else { tile_height }
for y in 0..= height { break }
let strip_rows = if rows_per_strip > height - row { height - row } else { rows_per_strip }
let expected = strip_rows * row_bytes
let raw = if compression == 1 {
let start = strip_offsets[strip]
let length = strip_byte_counts[strip]
if !tiff_range(data, start, length) || length != expected { return None }
let bytes : Array[Byte] = []
for i in 0.. Some(v); _ => None }
} else {
None
}
let raw = match raw { Some(bytes) => bytes; None => return None }
if predictor == 2 && !tiff_apply_horizontal_predictor(raw, row_bytes, strip_rows, samples) { return None }
for p in 0.. Int? {
if !tiff_range(data, p, 8) { return None }
let low = if little { tiff_u32(data, p, true) } else { tiff_u32(data, p + 4, false) }
let high = if little { tiff_u32(data, p + 4, true) } else { tiff_u32(data, p, false) }
if high != 0 || low < 0 { None } else { Some(low) }
}
fn tiff_big_values(
data : Array[Byte],
entry : Int,
kind : Int,
count : Int,
little : Bool,
) -> Array[Int]? {
if count <= 0 { return None }
let size = if kind == 3 { 2 } else if kind == 4 { 4 } else if kind == 16 { 8 } else { 0 }
if size == 0 { return None }
if count > data.length() / size { return None }
let total = count * size
let start = if total <= 8 {
entry + 12
} else {
match tiff_u64_int(data, entry + 12, little) { Some(v) => v; None => return None }
}
if !tiff_range(data, start, total) { return None }
let values : Array[Int] = []
for i in 0.. v; None => return None }
}
values.push(value)
}
Some(values)
}
/// Minimal BigTIFF subset: classic RGBA sample semantics, one uncompressed,
/// PackBits or LZW strip, and 64-bit offsets whose high word fits the host Int.
fn tiff_decode_bigtiff(data : Array[Byte]) -> Image? {
if data.length() < 16 { return None }
let little = data[0] == b'I' && data[1] == b'I'
if !little && !(data[0] == b'M' && data[1] == b'M') { return None }
if tiff_u16(data, 2, little) != 43 || tiff_u16(data, 4, little) != 8 || tiff_u16(data, 6, little) != 0 { return None }
let ifd = match tiff_u64_int(data, 8, little) { Some(v) => v; None => return None }
if ifd < 16 || !tiff_range(data, ifd, 8) { return None }
let count = match tiff_u64_int(data, ifd, little) { Some(v) => v; None => return None }
if count <= 0 || count > (data.length() - ifd - 8) / 20 || !tiff_range(data, ifd, 8 + count * 20 + 8) { return None }
let mut width = 0
let mut height = 0
let mut compression = 1
let mut photometric = 2
let mut samples = 0
let mut rows_per_strip = 0
let mut bits_ok = false
let mut bits_count = 0
let mut strip_offset = 0
let mut strip_bytes = 0
for i in 0.. v; None => return None }
let values = match tiff_big_values(data, entry, kind, n, little) { Some(v) => v; None => continue }
match tag {
256 => width = values[0]
257 => height = values[0]
258 => { bits_count = values.length(); bits_ok = values.length() > 0; for value in values { if value != 8 { bits_ok = false } } }
259 => compression = values[0]
262 => photometric = values[0]
273 => { if values.length() != 1 { return None }; strip_offset = values[0] }
277 => samples = values[0]
278 => rows_per_strip = values[0]
279 => { if values.length() != 1 { return None }; strip_bytes = values[0] }
284 => if values[0] != 1 { return None }
_ => ()
}
}
if width <= 0 || height <= 0 || !bits_ok || rows_per_strip < height ||
!tiff_range(data, strip_offset, strip_bytes) || (compression != 1 && compression != 5 && compression != 32773) ||
(photometric != 0 && photometric != 1 && photometric != 2) { return None }
if samples == 0 { samples = if photometric == 2 { 3 } else { 1 } }
if width > MAX_IMAGE_PIXELS / height { return None }
if samples != 1 && samples != 3 && samples != 4 { return None }
if (photometric == 2 && samples == 1) || (photometric != 2 && samples != 1) || bits_count != samples { return None }
let expected = width * height * samples
let raw = if compression == 1 {
if strip_bytes != expected { return None }
let bytes : Array[Byte] = []
for i in 0.. v; None => return None }
let out = Image::new(width, height)
for p in 0..<(width * height) {
let src = p * samples
let dst = p * 4
if samples == 1 {
let value = raw[src]
let v = if photometric == 0 { clamp_byte(255 - value.to_int()) } else { value }
out.data[dst] = v; out.data[dst + 1] = v; out.data[dst + 2] = v; out.data[dst + 3] = b'\xFF'
} else {
out.data[dst] = raw[src]; out.data[dst + 1] = raw[src + 1]; out.data[dst + 2] = raw[src + 2]
out.data[dst + 3] = if samples == 4 { raw[src + 3] } else { b'\xFF' }
}
}
Some(out)
}