1302 lines
46 KiB
Odin
1302 lines
46 KiB
Odin
package pdf
|
|
|
|
import "core:mem"
|
|
|
|
// Image XObjects and inline images (ISO 32000-2, 8.9).
|
|
//
|
|
// An Image is the image dictionary read once -- dimensions, bit depth, colour
|
|
// space, /Decode and the three masking mechanisms -- with its samples left
|
|
// undecoded. `image_decode` turns it into pixels the first time somebody
|
|
// actually paints it, and caches the result.
|
|
//
|
|
// WHAT COMES OUT
|
|
//
|
|
// * An ordinary image decodes to STRAIGHT-alpha RGBA8 (`Bitmap.n == 4`).
|
|
// Straight, not premultiplied, because that is what `blend_span_rgba8`
|
|
// documents itself as taking and what a caller uploading a texture wants.
|
|
// * An /ImageMask decodes to a single-channel coverage plane
|
|
// (`Bitmap.n == 1`), 255 where the stencil paints and 0 where it does not.
|
|
// There is no colour in it: the device paints it in the fill colour.
|
|
//
|
|
// THE PIPELINE, and where each step's classic bug lives
|
|
//
|
|
// 1. `stream_image_data` walks the byte filters and stops at the image
|
|
// filter, so a JPEG is decoded straight to pixels here instead of through
|
|
// an intermediate byte buffer.
|
|
// 2. Samples are unpacked MSB-first at 1, 2, 4, 8 or 16 bits per component,
|
|
// with each ROW padded out to a whole byte. Forgetting the row padding at
|
|
// a width that is not a multiple of 8/bpc shears the image diagonally.
|
|
// 3. /Decode maps each raw sample onto the colour space's component range.
|
|
// Indexed and Lab have defaults that are not 0..1, which is the usual
|
|
// source of an image that comes out inverted or wildly coloured;
|
|
// `colorspace_default_decode` knows them.
|
|
// 4. Alpha comes from /SMask, from a stencil /Mask, or from a colour-key
|
|
// /Mask array. A colour-key range is in SOURCE SAMPLE values, so it is
|
|
// compared before /Decode is applied, not after.
|
|
//
|
|
// EVERY FAILURE DEGRADES. A stream that will not decode, a JPEG 2000 or JBIG2
|
|
// codestream this library does not implement, an /SMask that is itself broken:
|
|
// each of those yields a fully transparent image of the right size and a
|
|
// Repair on the Document. `image_decode` returns an Error only when the caller
|
|
// passed something nil.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Limits
|
|
//
|
|
// Every one of these bounds a quantity that came out of the file.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// The widest or tallest image that will be decoded. A 600 dpi scan of a sheet
|
|
// of A0 is about 20000 pixels on its long side; 65536 is far beyond any of
|
|
// them and keeps `width * height` well inside an int on every target.
|
|
@(private)
|
|
MAX_IMAGE_DIM :: 1 << 16
|
|
// Total pixels in one decoded image: 64 megapixels, which is 256 MiB at RGBA8.
|
|
// Deliberately tighter than MAX_BITMAP_PIXELS, which bounds the page raster --
|
|
// a page can legitimately be huge, whereas an image that claims to be is a
|
|
// fuzzer or a corrupt /Width.
|
|
@(private)
|
|
MAX_IMAGE_PIXELS :: 1 << 26
|
|
// Depth of /SMask and /Mask chains followed. A mask never legitimately has a
|
|
// mask of its own; this only stops a file whose /SMask points back at the
|
|
// image that owns it from recursing.
|
|
@(private)
|
|
MAX_IMAGE_NESTING :: 4
|
|
|
|
// How much bigger an image's samples may be than the stream bytes they come
|
|
// out of, and the size below which the ratio is not looked at at all.
|
|
//
|
|
// Nothing in ISO 32000-2 relates /Width and /Height to the sample data, so a
|
|
// hundred-byte dictionary can ask for a quarter of a gigabyte of pixels and a
|
|
// full colour conversion over every one of them; the rule this library is
|
|
// written to is that a length out of a file is checked against the file before
|
|
// it is allocated for. This is that check, and it is deliberately loose,
|
|
// because streams really do decompress enormously: a blank page under CCITT G4
|
|
// codes a whole row in a handful of bits, and a flat image under Flate with a
|
|
// predictor is a run of zeroes, so both reach a thousandfold and neither is a
|
|
// broken file. What is left over is the shape that has no honest reading -- a
|
|
// few hundred bytes claiming tens of megabytes of samples.
|
|
//
|
|
// The floor keeps every ordinary image out of the test entirely: under a
|
|
// mebibyte of samples the decoded bitmap is at most 4 MiB whatever the stream
|
|
// says, which is not worth refusing a picture over.
|
|
@(private)
|
|
MAX_IMAGE_EXPANSION :: 4096
|
|
@(private)
|
|
IMAGE_EXPANSION_FLOOR :: 1 << 20
|
|
|
|
// IMAGE_CACHE_BUDGET is the total size of decoded pixels one Document holds.
|
|
//
|
|
// 64 MiB is a handful of full-page photographs, which is what a reader
|
|
// scrolling a brochure re-paints; a 300-page catalogue that would otherwise
|
|
// end up holding every photograph at once instead evicts the pages it has
|
|
// scrolled past. Eviction is least-recently-used and never touches the image
|
|
// currently being decoded.
|
|
@(private)
|
|
IMAGE_CACHE_BUDGET :: 64 << 20
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The image
|
|
// ---------------------------------------------------------------------------
|
|
|
|
Image :: struct {
|
|
width: int,
|
|
height: int,
|
|
// Bits per component: 1, 2, 4, 8 or 16. Always 1 when is_mask.
|
|
bpc: int,
|
|
cs: ^Colorspace,
|
|
|
|
// An /ImageMask is a 1-bit stencil painted in the current fill colour
|
|
// rather than an image with colours of its own.
|
|
is_mask: bool,
|
|
interpolate: bool,
|
|
// /Decode, 2*cs.n entries, or empty for the colour space's default.
|
|
decode: []f64,
|
|
|
|
// /SMask: a greyscale image supplying alpha.
|
|
smask: ^Image,
|
|
// /Mask as a stencil image.
|
|
mask: ^Image,
|
|
// /Mask as a colour-key range: 2*cs.n integers in source sample values.
|
|
color_key: []int,
|
|
has_color_key: bool,
|
|
|
|
// /Matte, on an /SMask whose owner's colour was pre-blended against it
|
|
// (ISO 32000-2, 11.6.5.3). Components are in the OWNER's colour space, so
|
|
// it is converted when the owner decodes, not when the mask loads.
|
|
matte: []f64,
|
|
has_matte: bool,
|
|
|
|
// The stream the samples come from, decoded lazily.
|
|
stream: ^Stream,
|
|
// True for a BI/ID/EI image, which is not shared and so is not entered in
|
|
// the Document's by-stream table.
|
|
is_inline: bool,
|
|
|
|
// Cache filled in by `image_decode`: straight-alpha RGBA8, or a 1-byte
|
|
// coverage plane when is_mask.
|
|
rgba: Bitmap,
|
|
decoded: bool,
|
|
failed: bool,
|
|
// Cache clock reading at the last `image_decode`, for LRU eviction.
|
|
last_use: u64,
|
|
}
|
|
|
|
// image_size is the decoded pixel size. It is not the size the image is drawn
|
|
// at: an image always covers the unit square of the current transform.
|
|
image_size :: proc(img: ^Image) -> (w, h: int) {
|
|
if img == nil {
|
|
return 0, 0
|
|
}
|
|
return img.width, img.height
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The decoded-image cache
|
|
//
|
|
// Two caches, with different lifetimes on purpose:
|
|
//
|
|
// * `by_stream` maps an image XObject's stream to its Image, so a logo drawn
|
|
// on all 300 pages is parsed once. Images are arena memory and cost a few
|
|
// hundred bytes each, so nothing is ever evicted from it. The key carries
|
|
// the ROLE as well as the stream, because a stream a /Mask points at is
|
|
// read as a 1-bit stencil while the same stream drawn by `Do` is read as
|
|
// a picture: two Images, and one cannot be both.
|
|
// * `live` holds the Images whose pixels are currently decoded. Those are
|
|
// megabytes each and come from a real allocator, so they are evicted
|
|
// least-recently-used once `bytes` exceeds `budget`.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Img_Key is a stream and the way it is being read. `stencil` is true for the
|
|
// second reading a /Mask entry forces on a stream that is not an /ImageMask.
|
|
@(private)
|
|
Img_Key :: struct {
|
|
stream: uintptr,
|
|
stencil: bool,
|
|
}
|
|
|
|
@(private)
|
|
Image_Cache :: struct {
|
|
by_stream: map[Img_Key]^Image,
|
|
live: [dynamic]^Image,
|
|
bytes: int,
|
|
// Zero means IMAGE_CACHE_BUDGET; a caller (or a test) may set it lower.
|
|
budget: int,
|
|
clock: u64,
|
|
}
|
|
|
|
// img_pixel_allocator is where decoded pixels come from. NOT the Document
|
|
// arena: the arena cannot free an individual allocation, so an image cached in
|
|
// it could never be evicted and the budget would be a fiction. `close` calls
|
|
// `image_cache_release`, which is what keeps this leak-free.
|
|
@(private)
|
|
img_pixel_allocator :: proc(doc: ^Document) -> mem.Allocator {
|
|
if doc != nil && doc.backing.procedure != nil {
|
|
return doc.backing
|
|
}
|
|
return context.allocator
|
|
}
|
|
|
|
// image_cache_release frees every decoded image. Called by `close`.
|
|
@(private)
|
|
image_cache_release :: proc(doc: ^Document) {
|
|
if doc == nil {
|
|
return
|
|
}
|
|
c := &doc.img_cache
|
|
for img in c.live {
|
|
if img == nil {
|
|
continue
|
|
}
|
|
bitmap_destroy(&img.rgba)
|
|
img.decoded = false
|
|
}
|
|
delete(c.live)
|
|
c.live = nil
|
|
c.bytes = 0
|
|
}
|
|
|
|
@(private)
|
|
img_cache_touch :: proc(doc: ^Document, img: ^Image) {
|
|
if doc == nil || img == nil {
|
|
return
|
|
}
|
|
doc.img_cache.clock += 1
|
|
img.last_use = doc.img_cache.clock
|
|
}
|
|
|
|
@(private)
|
|
img_cache_admit :: proc(doc: ^Document, img: ^Image) {
|
|
c := &doc.img_cache
|
|
if c.budget <= 0 {
|
|
c.budget = IMAGE_CACHE_BUDGET
|
|
}
|
|
if c.live == nil {
|
|
c.live = make([dynamic]^Image, 0, 16, img_pixel_allocator(doc))
|
|
}
|
|
append(&c.live, img)
|
|
c.bytes += len(img.rgba.pixels)
|
|
img_cache_touch(doc, img)
|
|
img_cache_evict(doc, img)
|
|
}
|
|
|
|
// img_cache_evict drops least-recently-used images until the budget is met.
|
|
// `keep` is never evicted, which is what makes the Bitmap `image_decode` just
|
|
// returned safe to use: the pointer stays valid until the next decode of a
|
|
// *different* image on this Document.
|
|
@(private)
|
|
img_cache_evict :: proc(doc: ^Document, keep: ^Image) {
|
|
c := &doc.img_cache
|
|
for c.bytes > c.budget {
|
|
victim := -1
|
|
oldest := max(u64)
|
|
for img, i in c.live {
|
|
if img == nil || img == keep {
|
|
continue
|
|
}
|
|
if img.last_use <= oldest {
|
|
oldest = img.last_use
|
|
victim = i
|
|
}
|
|
}
|
|
if victim < 0 {
|
|
// Nothing left but `keep`. A single image larger than the whole
|
|
// budget is held until the next decode rather than thrown away
|
|
// before its caller has drawn it.
|
|
return
|
|
}
|
|
v := c.live[victim]
|
|
c.bytes -= len(v.rgba.pixels)
|
|
bitmap_destroy(&v.rgba)
|
|
v.decoded = false
|
|
v.failed = false
|
|
unordered_remove(&c.live, victim)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Loading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// image_load reads an image XObject's dictionary.
|
|
//
|
|
// The Image is owned by the Document and must not be freed. The same stream
|
|
// read the same way always yields the same Image, so a picture placed on every
|
|
// page is read once and -- until the cache evicts it -- decoded once. A stream
|
|
// that is also named by some other image's /Mask has a second Image for that
|
|
// reading, because a stencil and a picture are not the same pixels.
|
|
image_load :: proc(doc: ^Document, stm: ^Stream, res: ^Dict) -> (^Image, Error) {
|
|
return img_load(doc, stm, res, 0)
|
|
}
|
|
|
|
// image_load_inline reads a BI/ID/EI inline image (ISO 32000-2, 8.9.7).
|
|
//
|
|
// `d` is the dictionary between BI and ID, which uses the abbreviated keys
|
|
// /W /H /BPC /CS /F /D /DP /IM, and `data` is the still-encoded bytes between
|
|
// ID and EI. `data` is copied, so it need not outlive the call. `res` is the
|
|
// page's /Resources, where a colour space name that is not one of the
|
|
// abbreviations is looked up.
|
|
//
|
|
// The Image is owned by the Document. Inline images are not shared, so unlike
|
|
// an XObject they are not cached by stream: each BI builds a new one.
|
|
image_load_inline :: proc(doc: ^Document, d: ^Dict, data: []byte, res: ^Dict) -> (img: ^Image, err: Error) {
|
|
if doc == nil || d == nil {
|
|
return nil, .Invalid_Argument
|
|
}
|
|
// Copied rather than borrowed: the caller's buffer is a window onto a
|
|
// content stream that a per-page arena may reset, and an inline image is
|
|
// required to be small (8.9.7 recommends 4 KiB), so the copy is cheap.
|
|
payload: []byte
|
|
if len(data) > 0 {
|
|
buf, aerr := make([]byte, len(data), doc.allocator)
|
|
if aerr != nil {
|
|
return nil, .Out_Of_Memory
|
|
}
|
|
copy(buf, data)
|
|
payload = buf
|
|
}
|
|
stm := new_stream(d, payload, doc.allocator)
|
|
img = img_build(doc, stm, res, 0) or_return
|
|
img.is_inline = true
|
|
return img, .None
|
|
}
|
|
|
|
@(private)
|
|
img_load :: proc(doc: ^Document, stm: ^Stream, res: ^Dict, depth: int) -> (img: ^Image, err: Error) {
|
|
if doc == nil || stm == nil {
|
|
return nil, .Invalid_Argument
|
|
}
|
|
if depth > MAX_IMAGE_NESTING {
|
|
add_repair(doc, .Recursion_Limit_Hit, 0, stm.ref)
|
|
return nil, .Recursion_Limit
|
|
}
|
|
|
|
key := Img_Key{uintptr(rawptr(stm)), false}
|
|
if cached, ok := doc.img_cache.by_stream[key]; ok {
|
|
return cached, .None
|
|
}
|
|
|
|
img = img_build(doc, stm, res, depth) or_return
|
|
img_cache_put(doc, key, img)
|
|
return img, .None
|
|
}
|
|
|
|
@(private)
|
|
img_cache_put :: proc(doc: ^Document, key: Img_Key, img: ^Image) {
|
|
if doc.img_cache.by_stream == nil {
|
|
doc.img_cache.by_stream = make(map[Img_Key]^Image, 16, doc.allocator)
|
|
}
|
|
doc.img_cache.by_stream[key] = img
|
|
}
|
|
|
|
// img_load_stencil reads a stream the way a /Mask entry means it: as a 1-bit
|
|
// stencil. 8.9.6.4 requires that stream to be an /ImageMask, and when it is
|
|
// this is exactly img_load.
|
|
//
|
|
// Producers that write a 1-bit (or 8-bit) /DeviceGray image instead mean the
|
|
// same thing -- the sample values are identical -- so it is read as a stencil
|
|
// anyway. What it must NOT do is write that decision back onto the Image the
|
|
// by-stream cache holds: that Image is what every other reference to the same
|
|
// stream gets, including a plain `Do` of it as a picture, and setting is_mask
|
|
// on it turns the picture into a one-channel stencil too. So the stencil
|
|
// reading is a second Image over the same stream, under its own key.
|
|
@(private)
|
|
img_load_stencil :: proc(doc: ^Document, stm: ^Stream, res: ^Dict, depth: int) ->
|
|
(img: ^Image, err: Error) {
|
|
m := img_load(doc, stm, res, depth) or_return
|
|
if m.is_mask {
|
|
return m, .None
|
|
}
|
|
key := Img_Key{uintptr(rawptr(stm)), true}
|
|
if cached, ok := doc.img_cache.by_stream[key]; ok {
|
|
return cached, .None
|
|
}
|
|
// Recorded once, where the second reading is built, and not again for
|
|
// every image that goes on to reference the same stencil.
|
|
add_repair(doc, .Bad_Resource, 0, stm.ref)
|
|
s := img_build(doc, stm, res, depth) or_return
|
|
s.is_mask = true
|
|
s.bpc = 1
|
|
img_cache_put(doc, key, s)
|
|
return s, .None
|
|
}
|
|
|
|
// img_samples_are_plausible reports whether a stream could hold the samples the
|
|
// dictionary claims. See MAX_IMAGE_EXPANSION. `n` and `bpc` are the component
|
|
// count and depth the samples will be read at; understating them only makes
|
|
// the test more permissive, which is the safe direction for a colour space
|
|
// that would not load.
|
|
@(private)
|
|
img_samples_are_plausible :: proc(stm: ^Stream, w, h, n, bpc: int) -> bool {
|
|
row := (w * max(n, 1) * max(bpc, 1) + 7) / 8
|
|
if row <= 0 || h <= 0 {
|
|
return false
|
|
}
|
|
need := row * h
|
|
if need <= IMAGE_EXPANSION_FLOOR {
|
|
return true
|
|
}
|
|
return need / MAX_IMAGE_EXPANSION <= len(stm.raw)
|
|
}
|
|
|
|
@(private)
|
|
img_build :: proc(doc: ^Document, stm: ^Stream, res: ^Dict, depth: int) -> (^Image, Error) {
|
|
d := stm.dict
|
|
if d == nil {
|
|
return nil, .Bad_Image
|
|
}
|
|
|
|
w := img_dim(doc, d, "Width", "W")
|
|
h := img_dim(doc, d, "Height", "H")
|
|
if w <= 0 || h <= 0 || w > MAX_IMAGE_DIM || h > MAX_IMAGE_DIM || w > MAX_IMAGE_PIXELS / h {
|
|
// Nothing here can be rendered and nothing can be guessed: an image
|
|
// with no dimensions is not a repairable image, it is not an image.
|
|
add_repair(doc, .Bad_Resource, 0, stm.ref)
|
|
return nil, .Bad_Image
|
|
}
|
|
|
|
img := new(Image, doc.allocator)
|
|
img.width = w
|
|
img.height = h
|
|
img.stream = stm
|
|
img.interpolate = img_flag(doc, d, "Interpolate", "I", false)
|
|
img.is_mask = img_flag(doc, d, "ImageMask", "IM", false)
|
|
|
|
if img.is_mask {
|
|
// 8.9.6.2: a stencil mask has no colour space and /BitsPerComponent,
|
|
// if present at all, shall be 1. Files write 8 anyway; the format is
|
|
// unambiguous, so the dictionary loses.
|
|
img.bpc = 1
|
|
if !img_samples_are_plausible(stm, w, h, 1, 1) {
|
|
add_repair(doc, .Bad_Resource, 0, stm.ref)
|
|
return nil, .Bad_Image
|
|
}
|
|
img.decode = img_decode_array(doc, d, 1, stm.ref)
|
|
return img, .None
|
|
}
|
|
|
|
img.bpc = int(img_number(doc, d, "BitsPerComponent", "BPC", 8))
|
|
switch img.bpc {
|
|
case 1, 2, 4, 8, 16:
|
|
case:
|
|
// A DCT or JPX image often omits /BitsPerComponent entirely, and 8 is
|
|
// the only depth either can hand back here.
|
|
img.bpc = 8
|
|
}
|
|
|
|
cs, cs_err := colorspace_load(doc, dict_get_alt(doc, d, "ColorSpace", "CS"), res)
|
|
if cs_err != .None || cs == nil {
|
|
// Left nil deliberately. A DCTDecode image whose /ColorSpace is broken
|
|
// still says how many components it has in its own frame header, and
|
|
// `img_fetch` picks the device space of that width; anything else
|
|
// falls back to the width implied by the sample count.
|
|
if dict_has(d, "ColorSpace") || dict_has(d, "CS") {
|
|
add_repair(doc, .Bad_Resource, 0, stm.ref)
|
|
}
|
|
} else {
|
|
img.cs = cs
|
|
}
|
|
|
|
n := img.cs != nil ? min(img.cs.n, MAX_COLOR_COMPONENTS) : 0
|
|
if !img_samples_are_plausible(stm, w, h, n, img.bpc) {
|
|
add_repair(doc, .Bad_Resource, 0, stm.ref)
|
|
return nil, .Bad_Image
|
|
}
|
|
img.decode = img_decode_array(doc, d, n, stm.ref)
|
|
|
|
img_load_smask(doc, img, d, res, depth)
|
|
img_load_mask(doc, img, d, res, depth, n)
|
|
return img, .None
|
|
}
|
|
|
|
@(private)
|
|
img_load_smask :: proc(doc: ^Document, img: ^Image, d: ^Dict, res: ^Dict, depth: int) {
|
|
sm := dict_stream(doc, d, "SMask")
|
|
if sm == nil {
|
|
return
|
|
}
|
|
m, err := img_load(doc, sm, res, depth + 1)
|
|
if err != .None || m == nil || m == img {
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
return
|
|
}
|
|
// /Matte lives in the soft mask's dictionary but its components are in the
|
|
// *owner's* colour space (11.6.5.3), which is why it is read here.
|
|
if mt := dict_array(doc, sm.dict, "Matte"); mt != nil && img.cs != nil {
|
|
want := min(img.cs.n, MAX_COLOR_COMPONENTS)
|
|
if array_len(mt) >= want && want > 0 {
|
|
vals := array_numbers(doc, mt, doc.allocator)
|
|
if len(vals) >= want && img_all_finite(vals[:want]) {
|
|
m.matte = vals[:want]
|
|
m.has_matte = true
|
|
}
|
|
}
|
|
}
|
|
img.smask = m
|
|
}
|
|
|
|
@(private)
|
|
img_load_mask :: proc(doc: ^Document, img: ^Image, d: ^Dict, res: ^Dict, depth, n: int) {
|
|
o := dict_get(doc, d, "Mask")
|
|
#partial switch v in o {
|
|
case ^Stream:
|
|
m, err := img_load_stencil(doc, v, res, depth + 1)
|
|
if err != .None || m == nil || m == img {
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
return
|
|
}
|
|
img.mask = m
|
|
|
|
case ^Array:
|
|
// 2*n integers, and they are SOURCE SAMPLE values -- indices into the
|
|
// raw sample range, not colours. Comparing them after /Decode has been
|
|
// applied is the classic way to get colour-key masking wrong.
|
|
want := 2 * n
|
|
if want <= 0 || array_len(v) < want {
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
return
|
|
}
|
|
keys, aerr := make([]int, want, doc.allocator)
|
|
if aerr != nil {
|
|
return
|
|
}
|
|
maxv := (int(1) << uint(clamp(img.bpc, 1, 16))) - 1
|
|
for i in 0 ..< want {
|
|
keys[i] = clamp(int(array_int(doc, v, i, 0)), 0, maxv)
|
|
}
|
|
img.color_key = keys
|
|
img.has_color_key = true
|
|
}
|
|
}
|
|
|
|
// img_decode_array reads /Decode (or its inline /D spelling). A wrong-length
|
|
// or non-finite array is dropped rather than honoured: the colour space's
|
|
// default is always a sane array, and half an applied /Decode is not.
|
|
@(private)
|
|
img_decode_array :: proc(doc: ^Document, d: ^Dict, n: int, ref: Ref) -> []f64 {
|
|
a, is_array := to_array(dict_get_alt(doc, d, "Decode", "D"))
|
|
if !is_array || a == nil {
|
|
return nil
|
|
}
|
|
if n <= 0 || array_len(a) < 2 * n {
|
|
add_repair(doc, .Bad_Resource, 0, ref)
|
|
return nil
|
|
}
|
|
vals := array_numbers(doc, a, doc.allocator)
|
|
if len(vals) < 2 * n || !img_all_finite(vals[:2 * n]) {
|
|
add_repair(doc, .Bad_Resource, 0, ref)
|
|
return nil
|
|
}
|
|
return vals[:2 * n]
|
|
}
|
|
|
|
@(private)
|
|
img_all_finite :: proc(vals: []f64) -> bool {
|
|
for v in vals {
|
|
if !is_finite(v) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// img_dim reads /Width or /Height, which real files write as reals often
|
|
// enough ("/Width 612.0") that a plain integer read would reject them.
|
|
@(private)
|
|
img_dim :: proc(doc: ^Document, d: ^Dict, key, alt: string) -> int {
|
|
return int(img_number(doc, d, key, alt, 0))
|
|
}
|
|
|
|
@(private)
|
|
img_number :: proc(doc: ^Document, d: ^Dict, key, alt: string, def: f64) -> f64 {
|
|
v, ok := to_number(dict_get_alt(doc, d, key, alt))
|
|
if !ok || !is_finite(v) {
|
|
return def
|
|
}
|
|
// Clamped before it is ever narrowed to an int: converting a f64 far
|
|
// outside the integer range is undefined, and /Width 1e300 is exactly the
|
|
// kind of thing a fuzzer writes.
|
|
return clamp(v, -1e9, 1e9)
|
|
}
|
|
|
|
@(private)
|
|
img_flag :: proc(doc: ^Document, d: ^Dict, key, alt: string, def: bool) -> bool {
|
|
if v, ok := to_bool(dict_get_alt(doc, d, key, alt)); ok {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Samples
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Image_Samples is a rectangle of unpacked-but-not-yet-interpreted samples.
|
|
// `row_bytes` is carried separately because it is not always
|
|
// `(w*n*bpc + 7)/8`: CCITTFaxDecode lays its rows out at /Columns, which is
|
|
// allowed to disagree with /Width.
|
|
@(private)
|
|
Image_Samples :: struct {
|
|
data: []byte,
|
|
w, h: int,
|
|
n: int,
|
|
bpc: int,
|
|
row_bytes: int,
|
|
}
|
|
|
|
// Image_Data is everything one decode needs, plus the scratch buffers it must
|
|
// give back. Release it with `img_release`.
|
|
@(private)
|
|
Image_Data :: struct {
|
|
s: Image_Samples,
|
|
cs: ^Colorspace,
|
|
// Effective /Decode: the dictionary's if it was usable, the colour space's
|
|
// default otherwise. Always 2*s.n entries.
|
|
dec: []f64,
|
|
raw: []byte, // from stream_image_data
|
|
conv: []byte, // from jpeg_decode or ccitt_decode
|
|
decbuf: []f64, // from colorspace_default_decode
|
|
}
|
|
|
|
@(private)
|
|
img_release :: proc(id: ^Image_Data, scratch: mem.Allocator) {
|
|
delete(id.raw, scratch)
|
|
delete(id.conv, scratch)
|
|
delete(id.decbuf, scratch)
|
|
id^ = {}
|
|
}
|
|
|
|
@(private)
|
|
img_row :: proc(s: Image_Samples, y: int) -> []byte {
|
|
if s.row_bytes <= 0 || y < 0 {
|
|
return nil
|
|
}
|
|
start := y * s.row_bytes
|
|
if start >= len(s.data) {
|
|
return nil
|
|
}
|
|
return s.data[start:min(start + s.row_bytes, len(s.data))]
|
|
}
|
|
|
|
// img_sample reads component `index` of a row, MSB first. A row shorter than
|
|
// the image claims reads as zeroes rather than as a fault: a truncated image
|
|
// stream is common and must still draw what arrived.
|
|
@(private)
|
|
img_sample :: proc(row: []byte, index, bpc: int) -> u32 {
|
|
switch bpc {
|
|
case 8:
|
|
return index < len(row) ? u32(row[index]) : 0
|
|
case 16:
|
|
i := index * 2
|
|
hi := i < len(row) ? u32(row[i]) : 0
|
|
lo := i + 1 < len(row) ? u32(row[i + 1]) : 0
|
|
return hi << 8 | lo
|
|
case 1, 2, 4:
|
|
bit := index * bpc
|
|
byi := bit >> 3
|
|
if byi >= len(row) {
|
|
return 0
|
|
}
|
|
shift := uint(8 - bpc - (bit & 7))
|
|
return (u32(row[byi]) >> shift) & u32((1 << uint(bpc)) - 1)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// img_fetch runs the filter chain and hands back samples ready to unpack.
|
|
@(private)
|
|
img_fetch :: proc(doc: ^Document, img: ^Image, scratch: mem.Allocator) -> (id: Image_Data, err: Error) {
|
|
data, fname, parms, derr := stream_image_data(doc, img.stream, scratch)
|
|
if derr != .None {
|
|
delete(data, scratch)
|
|
add_repair(doc, .Bad_Filter_Data, 0, img.stream.ref)
|
|
return {}, derr
|
|
}
|
|
id.raw = data
|
|
|
|
switch fname {
|
|
case "":
|
|
img_fetch_plain(doc, img, &id)
|
|
case "DCTDecode":
|
|
img_fetch_dct(doc, img, &id, scratch) or_return
|
|
case "CCITTFaxDecode":
|
|
img_fetch_ccitt(doc, img, &id, parms, scratch) or_return
|
|
case "JPXDecode", "JBIG2Decode":
|
|
// Neither codec is implemented. This is not an error that may kill a
|
|
// page: the caller turns it into a transparent placeholder.
|
|
return id, .Unsupported_Image
|
|
case:
|
|
return id, .Unsupported_Image
|
|
}
|
|
|
|
id.dec = img_effective_decode(img, id.cs, id.s.bpc, id.s.n, scratch, &id.decbuf)
|
|
return id, .None
|
|
}
|
|
|
|
@(private)
|
|
img_fetch_plain :: proc(doc: ^Document, img: ^Image, id: ^Image_Data) {
|
|
n := img.is_mask ? 1 : img_component_count(doc, img, len(id.raw))
|
|
bpc := img.is_mask ? 1 : img.bpc
|
|
id.cs = img.is_mask ? nil : img_effective_cs(img, n)
|
|
id.s = Image_Samples{
|
|
data = id.raw,
|
|
w = img.width,
|
|
h = img.height,
|
|
n = n,
|
|
bpc = bpc,
|
|
row_bytes = (img.width * n * bpc + 7) / 8,
|
|
}
|
|
}
|
|
|
|
@(private)
|
|
img_fetch_dct :: proc(doc: ^Document, img: ^Image, id: ^Image_Data, scratch: mem.Allocator) -> Error {
|
|
j, jerr := jpeg_decode(id.raw, scratch)
|
|
if jerr != .None {
|
|
add_repair(doc, .Bad_Filter_Data, 0, img.stream.ref)
|
|
return jerr
|
|
}
|
|
id.conv = j.data
|
|
if j.width <= 0 || j.height <= 0 || j.components <= 0 ||
|
|
j.width > MAX_IMAGE_DIM || j.height > MAX_IMAGE_DIM ||
|
|
j.width > MAX_IMAGE_PIXELS / j.height {
|
|
return .Bad_Image
|
|
}
|
|
if j.width != img.width || j.height != img.height {
|
|
// The frame header is the authority: it is what the samples are laid
|
|
// out to. An image always covers the unit square of the CTM, so taking
|
|
// the JPEG's size changes the resolution drawn, never the placement.
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
img.width = j.width
|
|
img.height = j.height
|
|
}
|
|
|
|
// The Adobe APP14 transform (`j.adobe_transform`) has already been undone
|
|
// by the decoder: a 3-component YCbCr scan arrives as RGB and a
|
|
// 4-component YCCK scan as CMYK. What is deliberately NOT undone there is
|
|
// the sample inversion Adobe applies to the CMYK JPEGs it embeds in PDFs.
|
|
// That inversion is expressed in this dictionary's /Decode array --
|
|
// [1 0 1 0 1 0 1 0] -- and is applied below like any other /Decode.
|
|
// Inverting here as well would double-apply it and turn every photograph
|
|
// into its own negative.
|
|
id.cs = img.cs
|
|
if id.cs == nil || min(id.cs.n, MAX_COLOR_COMPONENTS) != j.components {
|
|
id.cs = colorspace_device_n(j.components)
|
|
}
|
|
id.s = Image_Samples{
|
|
data = j.data,
|
|
w = j.width,
|
|
h = j.height,
|
|
n = j.components,
|
|
bpc = 8,
|
|
row_bytes = j.width * j.components,
|
|
}
|
|
return .None
|
|
}
|
|
|
|
@(private)
|
|
img_fetch_ccitt :: proc(doc: ^Document, img: ^Image, id: ^Image_Data, parms: ^Dict,
|
|
scratch: mem.Allocator) -> Error {
|
|
p := filter_ccitt_params(doc, parms)
|
|
if !dict_has(parms, "Columns") {
|
|
// /Columns defaults to 1728 because that is the width of a fax page,
|
|
// but inside a PDF the two are required to agree and a producer that
|
|
// omits it meant /Width. Taking 1728 for a 2544-pixel scan would shear
|
|
// every row.
|
|
p.columns = img.width
|
|
} else if p.columns != ccitt_columns(p) {
|
|
// Present, but not a width `ccitt_decode` will honour as written: it
|
|
// substitutes 1728 for a non-positive /Columns and clamps anything
|
|
// past MAX_CCITT_COLUMNS, and would then lay its rows out at that
|
|
// stride while this side read them back at another. /Width is the only
|
|
// other statement of the width in the file, and using it for both
|
|
// decodes the picture instead of merely agreeing about where the
|
|
// garbage starts.
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
p.columns = img.width
|
|
}
|
|
if p.rows <= 0 {
|
|
p.rows = img.height
|
|
}
|
|
if p.columns != img.width {
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
}
|
|
|
|
out, cerr := ccitt_decode(id.raw, p, scratch)
|
|
if cerr != .None {
|
|
delete(out, scratch)
|
|
add_repair(doc, .Bad_Filter_Data, 0, img.stream.ref)
|
|
return cerr
|
|
}
|
|
id.conv = out
|
|
|
|
// ccitt_decode emits exactly the 1-bit sample array an image expects: with
|
|
// /BlackIs1 false (the default) a black pixel is a 0 bit, which DeviceGray
|
|
// maps to 0.0, i.e. black. /BlackIs1 true exchanges the two in the sample
|
|
// data, and it is then the image's /Decode -- not this filter -- that says
|
|
// what the samples mean. So nothing is inverted here.
|
|
// CCITT is bi-level, so there is exactly one component whatever the
|
|
// dictionary's colour space claims.
|
|
id.cs = img.is_mask ? nil : img_effective_cs(img, 1)
|
|
id.s = Image_Samples{
|
|
data = out,
|
|
w = img.width,
|
|
h = img.height,
|
|
n = 1,
|
|
bpc = 1,
|
|
// Asked of the filter rather than recomputed: `p.columns` is now a
|
|
// width it honours verbatim, and going through the same procedure it
|
|
// does is what keeps the two from ever drifting apart again.
|
|
row_bytes = ccitt_row_bytes(p),
|
|
}
|
|
return .None
|
|
}
|
|
|
|
// img_effective_cs is the colour space to interpret `n` components in. A
|
|
// missing or unloadable /ColorSpace falls back to the device space of that
|
|
// width, which is what 8.6.5.5 says an ICCBased with no alternate does and the
|
|
// only guess available for an inline image with no /CS at all.
|
|
@(private)
|
|
img_effective_cs :: proc(img: ^Image, n: int) -> ^Colorspace {
|
|
if img.cs != nil && min(img.cs.n, MAX_COLOR_COMPONENTS) == n {
|
|
return img.cs
|
|
}
|
|
return colorspace_device_n(n)
|
|
}
|
|
|
|
// img_component_count is how many components a row holds. Normally the colour
|
|
// space says; when it could not be loaded the sample count is divided by the
|
|
// pixel count, which recovers the common "/ColorSpace is an unresolvable
|
|
// reference" case exactly.
|
|
@(private)
|
|
img_component_count :: proc(doc: ^Document, img: ^Image, nbytes: int) -> int {
|
|
if img.cs != nil {
|
|
return max(1, min(img.cs.n, MAX_COLOR_COMPONENTS))
|
|
}
|
|
for n in 1 ..= 4 {
|
|
need := ((img.width * n * img.bpc + 7) / 8) * img.height
|
|
if need == nbytes {
|
|
return n
|
|
}
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// img_effective_decode returns 2*n decode entries: the dictionary's if it had
|
|
// the right shape, the colour space's default otherwise. Indexed and Lab are
|
|
// exactly why the default cannot be assumed to be [0 1 0 1 ...].
|
|
@(private)
|
|
img_effective_decode :: proc(img: ^Image, cs: ^Colorspace, bpc, n: int,
|
|
scratch: mem.Allocator, owned: ^[]f64) -> []f64 {
|
|
if len(img.decode) >= 2 * n && n > 0 {
|
|
return img.decode[:2 * n]
|
|
}
|
|
if img.is_mask || cs == nil {
|
|
// A stencil's one component defaults to [0 1]: sample 0 paints.
|
|
buf, err := make([]f64, 2 * max(n, 1), scratch)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
for i in 0 ..< max(n, 1) {
|
|
buf[2 * i], buf[2 * i + 1] = 0, 1
|
|
}
|
|
owned^ = buf
|
|
return buf
|
|
}
|
|
d := colorspace_default_decode(cs, bpc, scratch)
|
|
if len(d) < 2 * n {
|
|
delete(d, scratch)
|
|
return nil
|
|
}
|
|
owned^ = d
|
|
return d[:2 * n]
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Decoding to pixels
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// image_decode decodes the image to pixels, caching the result on the Image.
|
|
//
|
|
// The result is straight-alpha RGBA8, or a one-channel coverage plane when
|
|
// `img.is_mask`. It belongs to the Document and must not be freed.
|
|
//
|
|
// LIFETIME: the pixels stay valid until the next `image_decode` of a
|
|
// *different* image on this Document, or until `close`. The cache evicts
|
|
// least-recently-used images to stay inside its byte budget, and never evicts
|
|
// the one it has just been asked for, so the ordinary
|
|
// "decode, draw, decode the next" loop is safe; holding one image's pixels
|
|
// across another's decode is not. The pointer itself never dangles -- it is
|
|
// into the Image, which lives as long as the Document -- so a caller that
|
|
// does hold one too long reads an empty Bitmap rather than freed memory.
|
|
//
|
|
// An image that cannot be decoded at all -- a broken stream, a JPEG 2000 or
|
|
// JBIG2 codestream -- comes back as a fully transparent image of the right
|
|
// size with a Repair on the Document. The Error return is for a nil argument.
|
|
image_decode :: proc(doc: ^Document, img: ^Image) -> (^Bitmap, Error) {
|
|
if doc == nil || img == nil {
|
|
return nil, .Invalid_Argument
|
|
}
|
|
if img.decoded {
|
|
img_cache_touch(doc, img)
|
|
return &img.rgba, .None
|
|
}
|
|
|
|
bmp, err := img_decode_uncached(doc, img)
|
|
if err != .None {
|
|
add_repair(doc, .Bad_Filter_Data, 0, img.stream != nil ? img.stream.ref : Ref{})
|
|
ph, perr := img_placeholder(doc, img)
|
|
if perr != .None {
|
|
return nil, perr
|
|
}
|
|
bmp = ph
|
|
img.failed = true
|
|
}
|
|
|
|
img.rgba = bmp
|
|
img.decoded = true
|
|
img_cache_admit(doc, img)
|
|
return &img.rgba, .None
|
|
}
|
|
|
|
// img_placeholder is a fully transparent image (or an empty stencil) of the
|
|
// right size, so a page whose picture will not decode still lays out.
|
|
@(private)
|
|
img_placeholder :: proc(doc: ^Document, img: ^Image) -> (Bitmap, Error) {
|
|
n := img.is_mask ? 1 : 4
|
|
w := clamp(img.width, 1, MAX_IMAGE_DIM)
|
|
h := clamp(img.height, 1, MAX_IMAGE_DIM)
|
|
if w > MAX_IMAGE_PIXELS / h {
|
|
w, h = 1, 1
|
|
}
|
|
return bitmap_make(w, h, n, img_pixel_allocator(doc))
|
|
}
|
|
|
|
@(private)
|
|
img_decode_uncached :: proc(doc: ^Document, img: ^Image) -> (bmp: Bitmap, err: Error) {
|
|
scratch := context.allocator
|
|
id, ferr := img_fetch(doc, img, scratch)
|
|
defer img_release(&id, scratch)
|
|
if ferr != .None {
|
|
return {}, ferr
|
|
}
|
|
if id.s.n <= 0 || len(id.dec) < 2 * id.s.n {
|
|
return {}, .Bad_Image
|
|
}
|
|
if img.width <= 0 || img.height <= 0 || img.width > MAX_IMAGE_PIXELS / img.height {
|
|
return {}, .Bad_Image
|
|
}
|
|
|
|
if img.is_mask {
|
|
stencil := bitmap_make(img.width, img.height, 1, img_pixel_allocator(doc)) or_return
|
|
img_unpack_stencil(id.s, id.dec, &stencil)
|
|
return stencil, .None
|
|
}
|
|
|
|
out := bitmap_make(img.width, img.height, 4, img_pixel_allocator(doc)) or_return
|
|
img_unpack_rgba(id.s, id.cs, id.dec, img.has_color_key ? img.color_key : nil, &out)
|
|
img_apply_alpha(doc, img, &out, scratch)
|
|
return out, .None
|
|
}
|
|
|
|
// img_unpack_stencil fills a one-byte coverage plane: 255 where the stencil
|
|
// paints. Sample 0 paints under the default /Decode [0 1]; /Decode [1 0]
|
|
// exchanges the two (8.9.6.2).
|
|
@(private)
|
|
img_unpack_stencil :: proc(s: Image_Samples, dec: []f64, out: ^Bitmap) {
|
|
d0 := len(dec) >= 2 ? dec[0] : 0
|
|
d1 := len(dec) >= 2 ? dec[1] : 1
|
|
for y in 0 ..< out.height {
|
|
row := img_row(s, y)
|
|
dst := bitmap_row(out, y)
|
|
if dst == nil {
|
|
continue
|
|
}
|
|
for x in 0 ..< min(out.width, len(dst)) {
|
|
v := img_sample(row, x, 1) == 0 ? d0 : d1
|
|
dst[x] = v < 0.5 ? 255 : 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// img_cs_is_identity_rgb reports whether the space maps a three-component
|
|
// value straight onto RGB. That is true of /DeviceRGB and of most of the
|
|
// ICCBased spaces that 24% of the corpus uses, which render through a
|
|
// DeviceRGB alternate. It is answered by PROBING the space rather than by
|
|
// pattern-matching its kind, so the fast path below is taken only where it
|
|
// provably agrees with the general one.
|
|
@(private)
|
|
img_cs_is_identity_rgb :: proc(cs: ^Colorspace) -> bool {
|
|
if cs == nil || min(cs.n, MAX_COLOR_COMPONENTS) != 3 {
|
|
return false
|
|
}
|
|
if cs.kind == .Device_RGB {
|
|
return true
|
|
}
|
|
#partial switch cs.kind {
|
|
case .Cal_RGB, .ICC_Based:
|
|
case:
|
|
return false
|
|
}
|
|
probes := [?][3]f64{
|
|
{0, 0, 0}, {1, 1, 1}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
|
|
{0.25, 0.5, 0.75}, {0.5, 0.2, 0.9},
|
|
}
|
|
for p in probes {
|
|
got := colorspace_to_rgb(cs, color_make(p[0], p[1], p[2]))
|
|
for i in 0 ..< 3 {
|
|
if abs(got[i] - p[i]) > 1.0 / 512.0 {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
@(private)
|
|
img_is_default_decode :: proc(dec: []f64, n: int) -> bool {
|
|
if len(dec) < 2 * n {
|
|
return false
|
|
}
|
|
for i in 0 ..< n {
|
|
if dec[2 * i] != 0 || dec[2 * i + 1] != 1 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
@(private)
|
|
img_unpack_rgba :: proc(s: Image_Samples, cs: ^Colorspace, dec: []f64, color_key: []int,
|
|
out: ^Bitmap) {
|
|
n := s.n
|
|
if n <= 0 || n > MAX_COLOR_COMPONENTS {
|
|
return
|
|
}
|
|
bpc := s.bpc
|
|
maxv := f64((int(1) << uint(clamp(bpc, 1, 16))) - 1)
|
|
if maxv <= 0 {
|
|
maxv = 1
|
|
}
|
|
|
|
// One component and at most 8 bits: 256 entries cover every possible
|
|
// sample, so the whole colour conversion -- an Indexed palette lookup, a
|
|
// Separation tint transform, a CalGray gamma -- is done at most 256 times
|
|
// instead of once per pixel.
|
|
lut: [256][3]u8
|
|
use_lut := n == 1 && bpc <= 8
|
|
if use_lut {
|
|
comp: Color
|
|
comp.n = 1
|
|
for i in 0 ..< (1 << uint(bpc)) {
|
|
comp.v[0] = dec[0] + f64(i) * (dec[1] - dec[0]) / maxv
|
|
f := colorspace_to_rgb(cs, comp)
|
|
lut[i] = {blend_quantize(f[0]), blend_quantize(f[1]), blend_quantize(f[2])}
|
|
}
|
|
}
|
|
direct_rgb := n == 3 && bpc == 8 && img_is_default_decode(dec, 3) && img_cs_is_identity_rgb(cs)
|
|
|
|
ck := len(color_key) >= 2 * n ? color_key : nil
|
|
|
|
comp: Color
|
|
comp.n = min(n, MAX_COLOR_COMPONENTS)
|
|
raws: [MAX_COLOR_COMPONENTS]u32
|
|
|
|
// A row the sample data does not reach at all reads as zeroes, so it is
|
|
// one colour from end to end -- and a declared /Height the stream comes
|
|
// nowhere near is the ordinary shape both of a truncated scan and of a
|
|
// dictionary written to make a reader work. Computing that colour once
|
|
// turns those rows into a fill instead of a colour conversion per pixel,
|
|
// which for a 32-colorant /DeviceN at 16 bits is the difference between a
|
|
// memory-speed loop and a minute of arithmetic. The bytes written are the
|
|
// same ones the general path would have written.
|
|
pad: [4]u8
|
|
{
|
|
rgb: [3]u8
|
|
switch {
|
|
case use_lut:
|
|
rgb = lut[0]
|
|
case direct_rgb:
|
|
case:
|
|
for c in 0 ..< comp.n {
|
|
// lo + 0 * (hi - lo) / maxv.
|
|
comp.v[c] = dec[2 * c]
|
|
}
|
|
f := colorspace_to_rgb(cs, comp)
|
|
rgb = {blend_quantize(f[0]), blend_quantize(f[1]), blend_quantize(f[2])}
|
|
}
|
|
alpha: u8 = 255
|
|
if ck != nil {
|
|
masked := true
|
|
for c in 0 ..< n {
|
|
if ck[2 * c] > 0 || ck[2 * c + 1] < 0 {
|
|
masked = false
|
|
break
|
|
}
|
|
}
|
|
if masked {
|
|
alpha = 0
|
|
}
|
|
}
|
|
pad = {rgb[0], rgb[1], rgb[2], alpha}
|
|
}
|
|
|
|
for y in 0 ..< out.height {
|
|
row := img_row(s, y)
|
|
dst := bitmap_row(out, y)
|
|
if dst == nil {
|
|
continue
|
|
}
|
|
if row == nil {
|
|
for x in 0 ..< out.width {
|
|
di := x * 4
|
|
if di + 4 > len(dst) {
|
|
break
|
|
}
|
|
copy(dst[di:di + 4], pad[:])
|
|
}
|
|
continue
|
|
}
|
|
for x in 0 ..< out.width {
|
|
di := x * 4
|
|
if di + 4 > len(dst) {
|
|
break
|
|
}
|
|
base := x * n
|
|
|
|
rgb: [3]u8
|
|
if use_lut {
|
|
v := img_sample(row, base, bpc)
|
|
raws[0] = v
|
|
rgb = lut[v & 0xff]
|
|
} else if direct_rgb {
|
|
raws[0] = img_sample(row, base, 8)
|
|
raws[1] = img_sample(row, base + 1, 8)
|
|
raws[2] = img_sample(row, base + 2, 8)
|
|
rgb = {u8(raws[0]), u8(raws[1]), u8(raws[2])}
|
|
} else {
|
|
for c in 0 ..< comp.n {
|
|
raws[c] = img_sample(row, base + c, bpc)
|
|
lo, hi := dec[2 * c], dec[2 * c + 1]
|
|
comp.v[c] = lo + f64(raws[c]) * (hi - lo) / maxv
|
|
}
|
|
f := colorspace_to_rgb(cs, comp)
|
|
rgb = {blend_quantize(f[0]), blend_quantize(f[1]), blend_quantize(f[2])}
|
|
}
|
|
|
|
alpha: u8 = 255
|
|
if ck != nil {
|
|
// Compared against the RAW samples, before /Decode: the ranges
|
|
// are sample values (8.9.6.4), not colour values.
|
|
masked := true
|
|
for c in 0 ..< n {
|
|
r := int(raws[c])
|
|
if r < ck[2 * c] || r > ck[2 * c + 1] {
|
|
masked = false
|
|
break
|
|
}
|
|
}
|
|
if masked {
|
|
alpha = 0
|
|
}
|
|
}
|
|
|
|
dst[di] = rgb[0]
|
|
dst[di + 1] = rgb[1]
|
|
dst[di + 2] = rgb[2]
|
|
dst[di + 3] = alpha
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Transparency: /SMask and a stencil /Mask
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
img_apply_alpha :: proc(doc: ^Document, img: ^Image, out: ^Bitmap, scratch: mem.Allocator) {
|
|
m := img.smask
|
|
is_stencil := false
|
|
if m == nil {
|
|
m = img.mask
|
|
is_stencil = true
|
|
}
|
|
if m == nil {
|
|
return
|
|
}
|
|
|
|
plane, err := img_alpha_plane(doc, m, is_stencil, out.width, out.height, scratch)
|
|
defer delete(plane, scratch)
|
|
if err != .None || len(plane) < out.width * out.height {
|
|
// A broken mask must not take the image with it: an opaque picture is
|
|
// a far better answer than a blank page.
|
|
add_repair(doc, .Bad_Resource, 0, img.stream.ref)
|
|
return
|
|
}
|
|
|
|
matte: [3]f64
|
|
unblend := false
|
|
if !is_stencil && m.has_matte && img.cs != nil {
|
|
c := color_make()
|
|
c.n = min(len(m.matte), MAX_COLOR_COMPONENTS)
|
|
for i in 0 ..< c.n {
|
|
c.v[i] = m.matte[i]
|
|
}
|
|
matte = colorspace_to_rgb(img.cs, c)
|
|
unblend = true
|
|
}
|
|
|
|
for y in 0 ..< out.height {
|
|
dst := bitmap_row(out, y)
|
|
prow := plane[y * out.width:]
|
|
for x in 0 ..< out.width {
|
|
di := x * 4
|
|
if di + 4 > len(dst) || x >= len(prow) {
|
|
break
|
|
}
|
|
a := prow[x]
|
|
// Both mechanisms multiply into whatever alpha colour-key masking
|
|
// already produced, rather than replacing it.
|
|
dst[di + 3] = u8((int(dst[di + 3]) * int(a) + 127) / 255)
|
|
if unblend && a != 0 && a != 255 {
|
|
// /Matte says the colour was stored pre-blended against the
|
|
// matte: c' = m + a*(c - m). Undo it (11.6.5.3). Done in RGB
|
|
// rather than in the image's own components, which is exact
|
|
// for the device spaces and a close approximation elsewhere.
|
|
for k in 0 ..< 3 {
|
|
mk := matte[k] * 255
|
|
v := mk + (f64(dst[di + k]) - mk) * 255 / f64(a)
|
|
dst[di + k] = blend_quantize(v / 255)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// img_alpha_plane decodes a mask to one byte of alpha per pixel, resampled to
|
|
// `w` x `h`. The result is allocated with `scratch` and belongs to the caller.
|
|
//
|
|
// Resampling is nearest-neighbour. An /SMask at a different resolution to its
|
|
// image is ordinary -- a photograph at 300 dpi with a 72 dpi cut-out mask, say
|
|
// -- and interpolating the mask would soften an edge the producer drew hard.
|
|
@(private)
|
|
img_alpha_plane :: proc(doc: ^Document, m: ^Image, is_stencil: bool, w, h: int,
|
|
scratch: mem.Allocator) -> ([]u8, Error) {
|
|
if m == nil || w <= 0 || h <= 0 || w > MAX_IMAGE_PIXELS / h {
|
|
return nil, .Invalid_Argument
|
|
}
|
|
id, err := img_fetch(doc, m, scratch)
|
|
defer img_release(&id, scratch)
|
|
if err != .None || id.s.n <= 0 || len(id.dec) < 2 * id.s.n {
|
|
return nil, err == .None ? .Bad_Image : err
|
|
}
|
|
if m.width <= 0 || m.height <= 0 || m.width > MAX_IMAGE_PIXELS / m.height {
|
|
return nil, .Bad_Image
|
|
}
|
|
|
|
src, aerr := make([]u8, m.width * m.height, scratch)
|
|
if aerr != nil {
|
|
return nil, .Out_Of_Memory
|
|
}
|
|
|
|
maxv := f64((int(1) << uint(clamp(id.s.bpc, 1, 16))) - 1)
|
|
if maxv <= 0 {
|
|
maxv = 1
|
|
}
|
|
stencil := is_stencil || m.is_mask
|
|
for y in 0 ..< m.height {
|
|
row := img_row(id.s, y)
|
|
for x in 0 ..< m.width {
|
|
v := f64(img_sample(row, x * id.s.n, id.s.bpc))
|
|
v = id.dec[0] + v * (id.dec[1] - id.dec[0]) / maxv
|
|
if stencil {
|
|
// A stencil /Mask marks what is masked OUT: sample 0 (decoded
|
|
// value 0) paints the base image, 1 hides it (8.9.6.4). That
|
|
// is the same polarity an /ImageMask uses.
|
|
src[y * m.width + x] = v < 0.5 ? 255 : 0
|
|
} else {
|
|
// An /SMask is DeviceGray by definition (11.6.5.2), so the
|
|
// decoded component is the alpha; no colour conversion.
|
|
src[y * m.width + x] = blend_quantize(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
if m.width == w && m.height == h {
|
|
return src, .None
|
|
}
|
|
defer delete(src, scratch)
|
|
|
|
out, oerr := make([]u8, w * h, scratch)
|
|
if oerr != nil {
|
|
return nil, .Out_Of_Memory
|
|
}
|
|
for y in 0 ..< h {
|
|
sy := clamp(y * m.height / h, 0, m.height - 1)
|
|
for x in 0 ..< w {
|
|
sx := clamp(x * m.width / w, 0, m.width - 1)
|
|
out[y * w + x] = src[sy * m.width + sx]
|
|
}
|
|
}
|
|
return out, .None
|
|
}
|