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 }