Files
pdf-odin-llm/filters.odin
T
2026-09-25 23:32:29 +02:00

804 lines
29 KiB
Odin

package pdf
import "core:mem"
// Stream decoding: the /Filter chain, the /DecodeParms that go with it, and
// the two predictors (ISO 32000-2, 7.4).
//
// The codecs themselves live in filter_*.odin. This file is only the plumbing:
// which filters a stream asks for, in what order, with which parameters, and
// what to do when one of them cannot deliver.
//
// The governing rule is that a stream which will not decode is an *empty*
// stream, not a failed document. A page whose one broken image will not
// inflate should still draw its text, so `stream_data` records a repair and
// hands back nothing rather than returning an error every caller would have to
// ignore. `Stream.state` is where the difference between "decoded to nothing"
// and "would not decode" is kept.
// A chain longer than this is not a document, it is an attempt to make a
// reader do work. Real files use one filter; two (an ASCII armour over a
// compressor) is the most that occurs in practice.
@(private)
MAX_FILTER_CHAIN :: 16
// ---------------------------------------------------------------------------
// Filter names
// ---------------------------------------------------------------------------
@(private)
Filter_Kind :: enum u8 {
Unknown = 0,
Ascii_Hex,
Ascii85,
Lzw,
Flate,
Run_Length,
Ccitt,
Dct,
Jpx,
Jbig2,
// /Crypt is a marker, not a transformation: decryption has already
// happened by the time a stream reaches this file.
Crypt,
}
// filter_kind classifies a filter name.
//
// The abbreviations are the ones ISO 32000-2 table 93 defines for inline
// images. They are accepted on ordinary streams too: a producer that emits
// /Fl outside an inline image is wrong, but the intent is not in doubt and no
// other filter name could be meant.
@(private)
filter_kind :: proc(name: string) -> Filter_Kind {
switch name {
case "ASCIIHexDecode", "AHx": return .Ascii_Hex
case "ASCII85Decode", "A85": return .Ascii85
case "LZWDecode", "LZW": return .Lzw
case "FlateDecode", "Fl": return .Flate
case "RunLengthDecode", "RL": return .Run_Length
case "CCITTFaxDecode", "CCF": return .Ccitt
case "DCTDecode", "DCT": return .Dct
case "JPXDecode": return .Jpx
case "JBIG2Decode": return .Jbig2
case "Crypt": return .Crypt
}
return .Unknown
}
// filter_name returns the unabbreviated spelling, so callers downstream of
// `stream_image_data` can compare against one name instead of two.
@(private)
filter_name :: proc(kind: Filter_Kind, fallback: string) -> string {
switch kind {
case .Ascii_Hex: return "ASCIIHexDecode"
case .Ascii85: return "ASCII85Decode"
case .Lzw: return "LZWDecode"
case .Flate: return "FlateDecode"
case .Run_Length: return "RunLengthDecode"
case .Ccitt: return "CCITTFaxDecode"
case .Dct: return "DCTDecode"
case .Jpx: return "JPXDecode"
case .Jbig2: return "JBIG2Decode"
case .Crypt: return "Crypt"
case .Unknown: return fallback
}
return fallback
}
// filter_is_image reports whether a filter produces an image rather than a
// byte stream. These four are the ones `stream_image_data` stops at: decoding
// them needs the image dictionary (dimensions, colour space, /Decode), and
// their output is samples, not bytes a content parser could read.
@(private)
filter_is_image :: proc(kind: Filter_Kind) -> bool {
#partial switch kind {
case .Dct, .Jpx, .Jbig2, .Ccitt: return true
}
return false
}
// filter_takes_parms reports whether a filter reads /DecodeParms at all. Used
// to place a parameter dictionary that a producer wrote where an array was
// required; see `filter_attach_parms`.
@(private)
filter_takes_parms :: proc(kind: Filter_Kind) -> bool {
#partial switch kind {
case .Lzw, .Flate, .Ccitt, .Jbig2, .Crypt: return true
}
return false
}
// ---------------------------------------------------------------------------
// The chain
// ---------------------------------------------------------------------------
@(private)
Filter_Step :: struct {
name: string,
kind: Filter_Kind,
parms: ^Dict,
// Position in the /Filter array, which is what /DecodeParms is indexed by.
// Not the same as the step's own index once a malformed entry is dropped.
slot: int,
}
// filter_chain fills `steps` with the stream's filters in application order.
//
// `ok` is false only when the file asks for more filters than
// MAX_FILTER_CHAIN; every other malformation is absorbed. A missing /Filter
// leaves n == 0 and ok == true.
@(private)
filter_chain :: proc(doc: ^Document, d: ^Dict, steps: []Filter_Step) -> (n: int, ok: bool) {
if d == nil || len(steps) == 0 {
return 0, true
}
// Inline images abbreviate both keys (ISO 32000-2, table 91). On an
// ordinary stream dictionary /F is an external file specification instead,
// but that is a string or a dictionary and neither survives the name test
// below, so looking is free.
filters := dict_get_alt(doc, d, "Filter", "F")
parms := dict_get_alt(doc, d, "DecodeParms", "DP")
#partial switch f in filters {
case Name:
steps[0] = Filter_Step{name = string(f), kind = filter_kind(string(f)), slot = 0}
n = 1
case ^Array:
count := array_len(f)
if count > len(steps) {
return 0, false
}
for i in 0 ..< count {
name, is_name := to_name(array_get(doc, f, i))
if !is_name {
// A null or a stray number inside /Filter. Dropping the entry
// keeps the rest of the chain usable; treating it as a filter
// would lose the stream over one bad element.
continue
}
steps[n] = Filter_Step{name = name, kind = filter_kind(name), slot = i}
n += 1
}
}
if n == 0 {
return 0, true
}
#partial switch p in parms {
case ^Array:
// Positional, and a short or over-long array is not fatal: a missing
// or null entry simply means "no parameters for that filter".
for i in 0 ..< n {
steps[i].parms = array_dict(doc, p, steps[i].slot)
}
case ^Dict:
filter_attach_parms(steps[:n], p)
case ^Stream:
// Never legal, but `to_dict` accepts a stream where a dictionary is
// wanted and a file that points /DecodeParms at a stream object still
// means its dictionary.
filter_attach_parms(steps[:n], p.dict)
}
return n, true
}
@(private)
filter_attach_parms :: proc(steps: []Filter_Step, p: ^Dict) {
if len(steps) == 1 {
steps[0].parms = p
return
}
// ISO 32000-2, 7.4.1 requires an array of dictionaries when /Filter is an
// array, but producers write a bare dictionary for chains such as
// [/ASCII85Decode /FlateDecode]. It can only have been meant for whichever
// filter reads parameters at all, so give it to the first one that does.
for i in 0 ..< len(steps) {
if filter_takes_parms(steps[i].kind) {
steps[i].parms = p
return
}
}
steps[0].parms = p
}
// ---------------------------------------------------------------------------
// Decode parameters
// ---------------------------------------------------------------------------
@(private)
Predictor_Parms :: struct {
predictor: int,
colors: int,
bpc: int,
columns: int,
}
// filter_predictor_parms reads the four predictor entries of ISO 32000-2
// table 8. Their defaults are what an absent /DecodeParms means, so a nil
// dictionary is the ordinary case rather than an error.
@(private)
filter_predictor_parms :: proc(doc: ^Document, parms: ^Dict) -> Predictor_Parms {
return Predictor_Parms{
predictor = int(dict_int(doc, parms, "Predictor", 1)),
colors = int(dict_int(doc, parms, "Colors", 1)),
bpc = int(dict_int(doc, parms, "BitsPerComponent", 8)),
columns = int(dict_int(doc, parms, "Columns", 1)),
}
}
// filter_ccitt_params reads the CCITTFaxDecode parameters of ISO 32000-2
// table 12. Exposed to the image layer, which gets the raw parameter
// dictionary back from `stream_image_data` and has to build the same struct.
@(private)
filter_ccitt_params :: proc(doc: ^Document, parms: ^Dict) -> Ccitt_Params {
p := ccitt_default_params()
p.k = int(dict_int(doc, parms, "K", 0))
p.columns = int(dict_int(doc, parms, "Columns", 1728))
p.rows = int(dict_int(doc, parms, "Rows", 0))
p.black_is_1 = dict_bool(doc, parms, "BlackIs1", false)
p.byte_align = dict_bool(doc, parms, "EncodedByteAlign", false)
p.end_of_line = dict_bool(doc, parms, "EndOfLine", false)
p.end_of_block = dict_bool(doc, parms, "EndOfBlock", true)
return p
}
// ---------------------------------------------------------------------------
// One filter
// ---------------------------------------------------------------------------
// apply_filter runs a single filter over `data`.
//
// `name` may be the full name or the inline-image abbreviation. `parms` is the
// matching /DecodeParms entry and may be nil, which means "all defaults".
//
// Ownership: when `err` is .None the result is allocated with `allocator` and
// belongs to the caller. When it is not, the result is either nil or `data`
// itself and must not be freed -- the pass-through errors (.Unknown_Filter,
// .Unsupported_Image, .Unsupported_Jpx) hand the bytes back untouched so a
// caller that knows better can still do something with them.
//
// A codec that fails part-way through returns .None with a short result, which
// is deliberate: half a content stream draws half a page, and that beats a
// blank one. Compare the length against what you expected if you need to know.
//
// This is also the allocator boundary, and that is the part worth reading
// twice. Every codec here grows a buffer it cannot size in advance, and the
// tolerant ones -- flate above all -- decode the same bytes several ways and
// keep the best attempt, dropping the losers with `delete`. Both habits are
// harmless against the heap and ruinous against `doc.allocator`: a
// `core:mem/virtual` arena answers .Free with .Mode_Not_Implemented, so
// `delete` on arena memory is a silent no-op rather than an error, and every
// rejected attempt and every superseded growth buffer stays until `close`.
// A damaged 256 KiB flate stream can strand megabytes that way, and the frees
// that would appear to clean it up are the ones ARCHITECTURE.md forbids.
//
// So a codec never sees the caller's allocator. It speculates in scratch,
// where its own `delete` calls do what they say, and exactly one buffer -- the
// winner, at its final length -- is copied into `allocator` afterwards. The
// caller's arena grows by the size of the output, not by the number of
// attempts it took to find it, and a codec added later inherits that without
// having to know about it.
apply_filter :: proc(doc: ^Document, name: string, data: []byte, parms: ^Dict,
allocator := context.allocator) -> (out: []byte, err: Error) {
scratch := context.allocator
if allocator == scratch {
// The caller already asked for the allocator the codec would have
// speculated in, so copying the result to itself would only double the
// peak for nothing.
return apply_filter_decode(doc, name, data, parms, scratch)
}
tmp, terr := apply_filter_decode(doc, name, data, parms, scratch)
if terr != .None {
// Per the ownership rule above, a failed filter hands back nil or
// `data` itself. Neither is ours to copy or to free.
return tmp, terr
}
defer delete(tmp, scratch)
return filter_copy(tmp, allocator)
}
// apply_filter_decode is the dispatch proper. `scratch` is throw-away memory
// that `apply_filter` frees on the way out, so a codec reached from here may
// allocate and free as freely as it likes; nothing it leaves behind outlives
// the call. Do not call this with a Document arena -- that is the whole point
// of it being separate.
@(private)
apply_filter_decode :: proc(doc: ^Document, name: string, data: []byte, parms: ^Dict,
scratch: mem.Allocator) -> (out: []byte, err: Error) {
switch filter_kind(name) {
case .Ascii_Hex:
return ascii_hex_decode(data, scratch)
case .Ascii85:
return ascii85_decode(data, scratch)
case .Run_Length:
return run_length_decode(data, scratch)
case .Flate:
pp := filter_predictor_parms(doc, parms)
if pp.predictor <= 1 {
return flate_decode(data, scratch)
}
// The un-predicted bytes are an intermediate: freed here rather than
// carried out, so a predicted stream costs one buffer and not two.
raw, ferr := flate_decode(data, scratch)
defer delete(raw, scratch)
if ferr != .None {
return nil, ferr
}
return apply_predictor(raw, pp.predictor, pp.colors, pp.bpc, pp.columns, scratch)
case .Lzw:
// /EarlyChange defaults to 1 (ISO 32000-2, table 8).
early := dict_int(doc, parms, "EarlyChange", 1) != 0
pp := filter_predictor_parms(doc, parms)
if pp.predictor <= 1 {
return lzw_decode(data, early, scratch)
}
raw, ferr := lzw_decode(data, early, scratch)
defer delete(raw, scratch)
if ferr != .None {
return nil, ferr
}
return apply_predictor(raw, pp.predictor, pp.colors, pp.bpc, pp.columns, scratch)
case .Ccitt:
return ccitt_decode(data, filter_ccitt_params(doc, parms), scratch)
case .Crypt:
// Decryption is done at parse time, by the time any stream reaches
// here, so /Crypt is the identity. ISO 32000-2, 7.4.10.
return filter_copy(data, scratch)
case .Dct, .Jbig2:
// Decoded by the image layer, which needs the image dictionary to make
// sense of the samples. `stream_image_data` is the way in.
return data, .Unsupported_Image
case .Jpx:
return data, .Unsupported_Jpx
case .Unknown:
return data, .Unknown_Filter
}
return data, .Unknown_Filter
}
@(private)
filter_copy :: proc(data: []byte, allocator: mem.Allocator) -> (out: []byte, err: Error) {
if len(data) == 0 {
return nil, .None
}
buf, aerr := make([]byte, len(data), allocator)
if aerr != nil {
return nil, .Out_Of_Memory
}
copy(buf, data)
return buf, .None
}
// ---------------------------------------------------------------------------
// Whole streams
// ---------------------------------------------------------------------------
// stream_data decodes a stream's whole filter chain and caches the result.
//
// The bytes belong to the Document (they are allocated in its arena, or are a
// sub-slice of the file when the stream has no filters) and must not be freed.
// Calling this twice returns the same slice without decoding again.
//
// A stream that will not decode is reported as empty, with a .Bad_Filter_Data
// repair on the Document and `Stream.state` left at .Failed; the error return
// is reserved for a caller mistake. This is the difference between a reader
// and a validator: one broken image must not cost a page.
//
// With a nil Document there is no arena to allocate from, so the cached bytes
// come from the context allocator and belong to the caller -- except for a
// stream with no filters, which still borrows the file. `Stream.raw` says
// which of the two you have.
stream_data :: proc(doc: ^Document, s: ^Stream) -> (data: []byte, err: Error) {
if s == nil {
return nil, .Invalid_Argument
}
switch s.state {
case .Decoded:
return s.decoded, .None
case .Failed:
return nil, .None
case .Pending:
}
allocator := context.allocator
if doc != nil {
allocator = doc.allocator
}
// An empty payload decodes to nothing whatever the chain says. Running
// flate over it would only manufacture a repair for a stream that is
// legitimately empty -- an empty content stream is written this way.
if len(s.raw) == 0 {
s.decoded = nil
s.state = .Decoded
return nil, .None
}
steps: [MAX_FILTER_CHAIN]Filter_Step
n, chain_ok := filter_chain(doc, s.dict, steps[:])
if !chain_ok {
return stream_failed(doc, s)
}
// Only the last stage's output is worth keeping, so the ones before it are
// scratch: each is freed as soon as the next stage has read it, and the
// survivor is copied into `allocator` at the end. Giving `allocator` every
// stage instead would strand the intermediates of an /ASCII85Decode over a
// /FlateDecode in the Document arena for the life of the document -- the
// arena cannot free them and ARCHITECTURE.md forbids asking it to -- and
// with no Document at all it would leak them outright.
scratch := context.allocator
cur := s.raw
tmp: []byte
filtered := false
defer delete(tmp, scratch)
for i in 0 ..< n {
if steps[i].kind == .Crypt {
// Identity here, and skipping it avoids copying the payload for
// nothing.
continue
}
next, ferr := apply_filter(doc, steps[i].name, cur, steps[i].parms, scratch)
if ferr != .None {
return stream_failed(doc, s)
}
delete(tmp, scratch)
tmp = next
cur = next
filtered = true
}
if !filtered {
// No /Filter at all, or a chain of nothing but /Crypt, which was
// already applied at parse time. The payload is still a sub-slice of
// the file, so there is nothing to copy and nothing to own -- and a
// caller with a nil Document must not free what it gets back here.
s.decoded = s.raw
s.state = .Decoded
return s.decoded, .None
}
out := cur
if allocator == scratch {
// Where the two are the same allocator -- a nil Document -- the last
// stage already produced the buffer the caller keeps. Hand ownership
// over instead of copying it and freeing the original.
tmp = nil
} else {
cerr: Error
out, cerr = filter_copy(cur, allocator)
if cerr != .None {
return stream_failed(doc, s)
}
}
s.decoded = out
s.state = .Decoded
return out, .None
}
@(private)
stream_failed :: proc(doc: ^Document, s: ^Stream) -> (data: []byte, err: Error) {
add_repair(doc, .Bad_Filter_Data, 0, s.ref)
s.decoded = nil
s.state = .Failed
return nil, .None
}
// stream_data_raw returns the stream's bytes exactly as stored -- still
// filtered, but already decrypted. Borrowed from the Document; do not free.
stream_data_raw :: proc(doc: ^Document, s: ^Stream) -> []byte {
if s == nil {
return nil
}
return s.raw
}
// stream_image_data decodes everything up to the stream's image filter.
//
// An image whose final filter is DCTDecode, JPXDecode, JBIG2Decode or
// CCITTFaxDecode is decoded straight to pixels by the raster path, which needs
// the image dictionary to do it. So this walks the chain applying only the
// byte filters, stops at the first image filter, and returns the bytes that
// filter would have been given, its unabbreviated name and its parameters.
// `image_filter` is "" when the chain held no image filter, in which case
// `data` is the finished sample bytes.
//
// The result is allocated with `allocator` and belongs to the caller. A JPX or
// JBIG2 codestream comes back intact and without an error: deciding what to do
// with a format this layer does not decode is the image layer's business.
stream_image_data :: proc(doc: ^Document, s: ^Stream, allocator := context.allocator) ->
(data: []byte, image_filter: string, parms: ^Dict, err: Error) {
if s == nil {
return nil, "", nil, .Invalid_Argument
}
steps: [MAX_FILTER_CHAIN]Filter_Step
n, chain_ok := filter_chain(doc, s.dict, steps[:])
if !chain_ok {
add_repair(doc, .Bad_Filter_Data, 0, s.ref)
return nil, "", nil, .Unknown_Filter
}
// Intermediates are scratch and are freed as the chain advances; only the
// last result is copied into `allocator`. Keeping them out of `allocator`
// means this is leak-free even when the caller hands us an arena it never
// frees individually.
cur := s.raw
tmp: []byte
defer delete(tmp, context.allocator)
for i in 0 ..< n {
if filter_is_image(steps[i].kind) {
out, cerr := filter_copy(cur, allocator)
if cerr != .None {
return nil, "", nil, cerr
}
return out, filter_name(steps[i].kind, steps[i].name), steps[i].parms, .None
}
if steps[i].kind == .Crypt {
continue
}
next, ferr := apply_filter(doc, steps[i].name, cur, steps[i].parms, context.allocator)
if ferr != .None {
add_repair(doc, .Bad_Filter_Data, 0, s.ref)
return nil, "", nil, ferr
}
delete(tmp, context.allocator)
tmp = next
cur = next
}
out, cerr := filter_copy(cur, allocator)
if cerr != .None {
return nil, "", nil, cerr
}
return out, "", nil, .None
}
// ---------------------------------------------------------------------------
// Predictors
//
// A predictor is not a filter: it is a reversible transform the encoder ran
// over the samples first, so that the compressor behind it sees mostly zeroes.
// ISO 32000-2, 7.4.4.4 defines two, both borrowed wholesale -- predictor 2 is
// TIFF 6.0's horizontal differencing, and 10..15 are the five PNG line filters
// (RFC 2083, 6). The PNG value is the *maximum* filter the encoder used;
// the actual filter is the first byte of each row, so all six PNG values
// behave identically on decode.
// ---------------------------------------------------------------------------
// DeviceN tops out at 32 colorants, and no predictor has ever been applied to
// anything wider.
@(private)
MAX_PREDICTOR_COLORS :: 32
@(private)
MAX_PREDICTOR_COLUMNS :: 1 << 20
// 16 MiB for a single row. An xref stream's rows are a handful of bytes and an
// image's are tens of kilobytes; this only exists so a /Columns out of a fuzzer
// cannot ask for an allocation the machine will not survive.
@(private)
MAX_PREDICTOR_ROW_BYTES :: 1 << 24
@(private)
MAX_PREDICTOR_OUTPUT :: 1 << 30
// apply_predictor undoes a /Predictor transform.
//
// `predictor` is the /Predictor value: 0 or 1 for none, 2 for TIFF horizontal
// differencing, 10..15 for the PNG line filters. `colors`, `bpc` and `columns`
// are /Colors, /BitsPerComponent and /Columns.
//
// Parameters that no image could have are rejected with .Bad_Predictor rather
// than clamped: unlike a damaged payload, a nonsensical /Columns means the
// output would be misaligned garbage, and garbage that looks like data is
// worse than nothing.
//
// A final row cut short by a truncated payload is zero-padded and kept.
// Damaged xref streams rely on this -- the last entry of a truncated xref
// stream is still worth having.
//
// The result is allocated with `allocator` and belongs to the caller.
apply_predictor :: proc(data: []byte, predictor, colors, bpc, columns: int,
allocator := context.allocator) -> (out: []byte, err: Error) {
png := false
switch {
case predictor == 0, predictor == 1:
// /Predictor 0 is not defined; files that write it mean "none".
return filter_copy(data, allocator)
case predictor == 2:
case predictor >= 10 && predictor <= 15:
png = true
case:
return nil, .Bad_Predictor
}
if colors < 1 || colors > MAX_PREDICTOR_COLORS {
return nil, .Bad_Predictor
}
switch bpc {
case 1, 2, 4, 8, 16:
case:
return nil, .Bad_Predictor
}
if columns < 1 || columns > MAX_PREDICTOR_COLUMNS {
return nil, .Bad_Predictor
}
row_bits := i64(columns) * i64(colors) * i64(bpc)
if row_bits > i64(MAX_PREDICTOR_ROW_BYTES) * 8 {
return nil, .Bad_Predictor
}
row_bytes := int((row_bits + 7) / 8)
if row_bytes < 1 {
return nil, .Bad_Predictor
}
if len(data) == 0 {
return nil, .None
}
// A PNG row carries a leading filter tag; a TIFF row does not.
stride := row_bytes + 1 if png else row_bytes
rows := (len(data) + stride - 1) / stride
total := rows * row_bytes
if total < 1 || total > MAX_PREDICTOR_OUTPUT {
return nil, .Bad_Predictor
}
buf, aerr := make([]byte, total, allocator)
if aerr != nil {
return nil, .Out_Of_Memory
}
for r in 0 ..< rows {
row := buf[r * row_bytes:][:row_bytes]
src := r * stride + (1 if png else 0)
// `buf` came from make and is zeroed, so a short final row is padded
// by simply not copying over it.
if src < len(data) {
copy(row, data[src:min(src + row_bytes, len(data))])
}
if png {
tag := 0
if r * stride < len(data) {
tag = int(data[r * stride])
}
prev: []byte
if r > 0 {
prev = buf[(r - 1) * row_bytes:][:row_bytes]
}
predictor_png_row(row, prev, tag, max(1, (colors * bpc + 7) / 8))
} else {
predictor_tiff_row(row, colors, bpc, columns)
}
}
return buf, .None
}
// predictor_png_row reverses one PNG line filter in place. `prev` is the
// already reconstructed line above, or nil on the first line, where the
// filters are defined to read zeroes. `bpp` is the byte distance to the pixel
// on the left, at least 1 (RFC 2083, 6.1).
@(private)
predictor_png_row :: proc(row, prev: []byte, tag, bpp: int) {
n := len(row)
switch tag {
case 0: // None
case 1: // Sub
for i in bpp ..< n {
row[i] += row[i - bpp]
}
case 2: // Up
if prev == nil {
return
}
for i in 0 ..< n {
row[i] += prev[i]
}
case 3: // Average
for i in 0 ..< n {
left := i >= bpp ? int(row[i - bpp]) : 0
up := prev != nil ? int(prev[i]) : 0
row[i] += byte((left + up) / 2)
}
case 4: // Paeth
for i in 0 ..< n {
a := i >= bpp ? int(row[i - bpp]) : 0
b := prev != nil ? int(prev[i]) : 0
c := prev != nil && i >= bpp ? int(prev[i - bpp]) : 0
row[i] += byte(predictor_paeth(a, b, c))
}
case:
// An undefined tag. Encoders do not emit one, but a byte lost from the
// front of the payload shifts every tag, and reading the row as
// unfiltered keeps the rest of the stream in step instead of failing
// the whole stream on one byte.
}
}
// predictor_paeth is the predictor of RFC 2083, 6.6, verbatim: of the three
// neighbours it picks whichever the linear estimate a+b-c is nearest to,
// resolving ties towards a then b.
@(private)
predictor_paeth :: proc(a, b, c: int) -> int {
p := a + b - c
pa := abs(p - a)
pb := abs(p - b)
pc := abs(p - c)
if pa <= pb && pa <= pc {
return a
}
if pb <= pc {
return b
}
return c
}
// predictor_tiff_row reverses TIFF horizontal differencing in place: every
// component is stored as its difference from the component `colors` positions
// earlier in the same row, modulo the component's own width.
@(private)
predictor_tiff_row :: proc(row: []byte, colors, bpc, columns: int) {
n := columns * colors
switch bpc {
case 8:
limit := min(n, len(row))
for i in colors ..< limit {
row[i] += row[i - colors]
}
case 16:
limit := min(n, len(row) / 2)
for i in colors ..< limit {
prev := u16(row[(i - colors) * 2]) << 8 | u16(row[(i - colors) * 2 + 1])
cur := u16(row[i * 2]) << 8 | u16(row[i * 2 + 1])
v := cur + prev
row[i * 2] = byte(v >> 8)
row[i * 2 + 1] = byte(v)
}
case 1, 2, 4:
// Sub-byte components are packed high bit first and never straddle a
// byte, since 1, 2 and 4 all divide 8.
mask := (1 << uint(bpc)) - 1
limit := min(n, len(row) * 8 / bpc)
for i in colors ..< limit {
v := predictor_bits_get(row, i - colors, bpc) + predictor_bits_get(row, i, bpc)
predictor_bits_set(row, i, bpc, v & mask)
}
}
}
@(private)
predictor_bits_get :: proc(row: []byte, index, bpc: int) -> int {
bit := index * bpc
at := bit >> 3
if at < 0 || at >= len(row) {
return 0
}
shift := uint(8 - bpc - (bit & 7))
return int(row[at] >> shift) & ((1 << uint(bpc)) - 1)
}
@(private)
predictor_bits_set :: proc(row: []byte, index, bpc, value: int) {
bit := index * bpc
at := bit >> 3
if at < 0 || at >= len(row) {
return
}
shift := uint(8 - bpc - (bit & 7))
mask := byte(((1 << uint(bpc)) - 1) << shift)
row[at] = (row[at] & ~mask) | (byte(value) << shift) & mask
}