1533 lines
49 KiB
Odin
1533 lines
49 KiB
Odin
package pdf
|
|
|
|
import "core:math"
|
|
|
|
// Shadings (ISO 32000-2, 8.7.4.5). Types 1-3 are evaluated per pixel from a
|
|
// function; types 4-7 are meshes that are decomposed into triangles.
|
|
//
|
|
// HOW A SHADING REACHES THE PIXELS
|
|
//
|
|
// Everything here paints through `blend_span_rgba8`, one horizontal run at a
|
|
// time, in straight RGBA8 -- the same road an image takes, so a shading, an
|
|
// image and a fill all round identically and a page cannot show a seam where
|
|
// one meets another. Runs are assembled in `Shade_Ctx.row`, one scratch row as
|
|
// wide as the clip, and a pixel the shading does not cover is left with alpha
|
|
// zero and skipped rather than painted transparent, because "not painted" and
|
|
// "painted with alpha 0" differ under every blend mode but Normal.
|
|
//
|
|
// Types 1-3 walk the clip box row by row, mapping each pixel centre back
|
|
// through the inverse CTM into the shading's own space. Types 4-7 read
|
|
// triangles out of the mesh stream and scan-convert each one with a top-left
|
|
// fill rule at pixel centres. That rule is not decoration: adjacent triangles
|
|
// share an edge exactly, and a pixel centre landing on a shared edge must be
|
|
// claimed by exactly one of them or the mesh grows either a bright seam (the
|
|
// pixel painted twice, visible whenever alpha < 1) or a dark one (painted by
|
|
// neither). Triangles are drawn without anti-aliasing for the same reason --
|
|
// two anti-aliased edges meeting would composite twice and leave a lighter
|
|
// line down every shared edge. Triangles that genuinely overlap -- which only
|
|
// a self-intersecting mesh produces -- are composited in the order the stream
|
|
// lists them, so the later one shows, which is what the format asks for; with
|
|
// a constant alpha below 1 the overlap is painted twice, and correcting that
|
|
// would mean rendering the whole shading into a buffer of its own for a case
|
|
// no real file contains.
|
|
//
|
|
// WHAT IS NOT DONE HERE
|
|
//
|
|
// /Background is parsed but never painted. It applies only when a shading is
|
|
// used as a pattern's fill, and even there only outside the shading's own
|
|
// extent; no viewer this library was checked against paints it, and painting
|
|
// it would make `sh` disagree with `scn` for the same shading dictionary.
|
|
// /AntiAlias is likewise parsed and ignored -- it is a hint, and the mesh
|
|
// rasteriser's fill rule is chosen for seam-freedom rather than smoothness.
|
|
|
|
Shading_Kind :: enum u8 {
|
|
Function_Based = 1,
|
|
Axial = 2,
|
|
Radial = 3,
|
|
Free_Triangle = 4,
|
|
Lattice_Triangle = 5,
|
|
Coons = 6,
|
|
Tensor = 7,
|
|
}
|
|
|
|
Shading :: struct {
|
|
kind: Shading_Kind,
|
|
cs: ^Colorspace,
|
|
// One function producing cs.n outputs, or cs.n functions each producing
|
|
// one. Both spellings are legal and both occur.
|
|
funcs: []^Function,
|
|
|
|
// /Background, painted outside the shading's own extent when the shading
|
|
// is used as a pattern (it is ignored by the `sh` operator).
|
|
background: []f64,
|
|
has_background: bool,
|
|
// /BBox in the shading's own space.
|
|
bbox: Rect,
|
|
has_bbox: bool,
|
|
antialias: bool,
|
|
|
|
// Type 1.
|
|
domain: [4]f64,
|
|
mtx: Matrix,
|
|
|
|
// Types 2 and 3: /Coords is 4 numbers for axial, 6 for radial.
|
|
coords: [6]f64,
|
|
// /Domain, defaulting to [0 1].
|
|
t0, t1: f64,
|
|
extend: [2]bool,
|
|
|
|
// Types 4-7: the mesh stream and its sample layout.
|
|
stream: ^Stream,
|
|
bits_per_coord: int,
|
|
bits_per_component: int,
|
|
bits_per_flag: int,
|
|
decode: []f64,
|
|
// Type 5 only.
|
|
vertices_per_row: int,
|
|
|
|
// Colour values per mesh vertex: 1 when /Function maps a parametric value,
|
|
// otherwise one per component of `cs`. It fixes the size of every vertex
|
|
// in the stream, so it is resolved once at load time.
|
|
n_comp: int,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Limits
|
|
//
|
|
// Every one of these bounds a number that came out of a file.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// SHADE_LUT_SIZE is how finely the colour function of an axial or radial
|
|
// shading is sampled. The function is 1-D there, so sampling it once per
|
|
// entry and looking the colour up per pixel turns a type 4 PostScript
|
|
// calculator running several million times into 256 runs. 256 entries put the
|
|
// error below the 8-bit quantisation the output has anyway.
|
|
@(private)
|
|
SHADE_LUT_SIZE :: 256
|
|
|
|
// Vertices one mesh stream may yield. A megabyte of 16-bit vertices is already
|
|
// far past any real gradient mesh.
|
|
@(private)
|
|
MAX_MESH_VERTICES :: 1 << 20
|
|
|
|
// /VerticesPerRow. Two is the minimum that forms a triangle; the cap is what
|
|
// stops a four-byte integer from sizing an allocation.
|
|
@(private)
|
|
MAX_MESH_ROW :: 1 << 14
|
|
|
|
@(private)
|
|
MAX_MESH_TRIANGLES :: 1 << 20
|
|
|
|
@(private)
|
|
MAX_MESH_PATCHES :: 1 << 16
|
|
|
|
// Steps a patch is subdivided into along each parameter. 32 gives 2048
|
|
// triangles for the largest patch, which is smooth well past 300 dpi.
|
|
@(private)
|
|
MAX_PATCH_SUBDIV :: 32
|
|
|
|
// Device pixels a patch aims to put between two subdivision steps.
|
|
@(private)
|
|
PATCH_STEP_PIXELS :: 3.0
|
|
|
|
// Pixels one shading may examine. This is not a quality knob -- a full page at
|
|
// 600 dpi is under 35 million -- it is the stop on a mesh built to make the
|
|
// rasteriser walk the clip a thousand times over.
|
|
@(private)
|
|
SHADE_PIXEL_FUEL :: 1 << 26
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Loading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// shading_load builds the shading `obj` describes: a dictionary for types 1-3,
|
|
// a stream for types 4-7 (whose dictionary carries the same entries).
|
|
//
|
|
// `res` is the /Resources dictionary the shading was named from, needed only
|
|
// to resolve a colour space given as a bare name.
|
|
//
|
|
// The result is owned by the Document and must not be freed. Nothing is
|
|
// cached: unlike a colour space or a function, a shading is built once per
|
|
// `sh` operator or once per pattern, and the mesh stream it points at is
|
|
// decoded lazily by `stream_data`, which does its own caching.
|
|
shading_load :: proc(doc: ^Document, obj: Object, res: ^Dict) -> (out: ^Shading, err: Error) {
|
|
if doc == nil {
|
|
return nil, .Invalid_Argument
|
|
}
|
|
o := resolve(doc, obj)
|
|
d, has_dict := to_dict(o)
|
|
if !has_dict {
|
|
return nil, .Bad_Shading
|
|
}
|
|
|
|
st := dict_int(doc, d, "ShadingType", 0)
|
|
if st < 1 || st > 7 {
|
|
return nil, .Bad_Shading
|
|
}
|
|
|
|
sh := new(Shading, doc.allocator)
|
|
sh.kind = Shading_Kind(u8(st))
|
|
|
|
cs, cs_err := colorspace_load(doc, dict_get(doc, d, "ColorSpace"), res)
|
|
// A shading whose colour space will not load cannot be guessed at: for a
|
|
// mesh the component count is what fixes the size of every vertex in the
|
|
// stream, so a wrong guess does not degrade, it garbles.
|
|
if cs_err != .None || cs == nil || cs.n < 1 || cs.n > MAX_COLOR_COMPONENTS {
|
|
return nil, .Bad_Shading
|
|
}
|
|
// 8.7.4.5.1: the colour space of a shading shall not be a Pattern space.
|
|
if cs.kind == .Pattern {
|
|
return nil, .Bad_Shading
|
|
}
|
|
sh.cs = cs
|
|
|
|
sh.funcs = shade_load_functions(doc, dict_get(doc, d, "Function"))
|
|
// Types 1-3 have no colour of their own; without a function there is
|
|
// nothing to paint with.
|
|
if len(sh.funcs) == 0 && (sh.kind == .Function_Based || sh.kind == .Axial || sh.kind == .Radial) {
|
|
return nil, .Bad_Shading
|
|
}
|
|
|
|
if ba := dict_array(doc, d, "Background"); array_len(ba) > 0 {
|
|
n := min(array_len(ba), cs.n)
|
|
bg := make([]f64, n, doc.allocator)
|
|
for i in 0 ..< n {
|
|
bg[i] = clamp_finite(array_num(doc, ba, i), 0)
|
|
}
|
|
sh.background = bg
|
|
sh.has_background = true
|
|
}
|
|
|
|
if bb, bb_ok := object_to_rect(doc, dict_get(doc, d, "BBox")); bb_ok {
|
|
sh.bbox = bb
|
|
sh.has_bbox = true
|
|
}
|
|
sh.antialias = dict_bool(doc, d, "AntiAlias")
|
|
|
|
switch sh.kind {
|
|
case .Function_Based:
|
|
shade_load_type1(doc, sh, d)
|
|
case .Axial, .Radial:
|
|
shade_load_axial_radial(doc, sh, d) or_return
|
|
case .Free_Triangle, .Lattice_Triangle, .Coons, .Tensor:
|
|
stm, is_stream := to_stream(o)
|
|
if !is_stream {
|
|
return nil, .Bad_Shading
|
|
}
|
|
sh.stream = stm
|
|
shade_load_mesh(doc, sh, d) or_return
|
|
}
|
|
return sh, .None
|
|
}
|
|
|
|
// shade_load_functions reads /Function, which is either one function with as
|
|
// many outputs as the colour space has components or an array of that many
|
|
// one-output functions. Both spellings are legal and both occur, so the array
|
|
// is what is stored and the count is what tells them apart.
|
|
//
|
|
// The array form is INDEXED BY COMPONENT, so its length and the position of
|
|
// every entry in it are load-bearing: a sub-function that will not load holds
|
|
// its slot as nil rather than being dropped. An array in which nothing loads
|
|
// comes back empty, which for types 1-3 is what makes the shading fail.
|
|
@(private)
|
|
shade_load_functions :: proc(doc: ^Document, obj: Object) -> []^Function {
|
|
if obj == nil {
|
|
return nil
|
|
}
|
|
if a, is_array := to_array(obj); is_array {
|
|
count := min(array_len(a), MAX_COLOR_COMPONENTS)
|
|
if count == 0 {
|
|
return nil
|
|
}
|
|
out, aerr := make([]^Function, count, doc.allocator)
|
|
if aerr != nil {
|
|
return nil
|
|
}
|
|
any := false
|
|
for i in 0 ..< count {
|
|
f, err := function_load(doc, array_get(doc, a, i))
|
|
if err != .None || f == nil {
|
|
// One broken sub-function must not lose the others, and it
|
|
// must not move them either: the slot is left nil so that
|
|
// every surviving function stays on the component the file
|
|
// wrote it for, and only the component this one would have
|
|
// supplied reads as zero. Dropping the entry instead would
|
|
// shift every later function down one index and repaint the
|
|
// whole shading in the wrong colours.
|
|
add_repair(doc, .Bad_Resource, 0)
|
|
continue
|
|
}
|
|
out[i] = f
|
|
any = true
|
|
}
|
|
if !any {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|
|
f, err := function_load(doc, obj)
|
|
if err != .None || f == nil {
|
|
return nil
|
|
}
|
|
one := make([]^Function, 1, doc.allocator)
|
|
one[0] = f
|
|
return one
|
|
}
|
|
|
|
@(private)
|
|
shade_load_type1 :: proc(doc: ^Document, sh: ^Shading, d: ^Dict) {
|
|
sh.domain = {0, 1, 0, 1}
|
|
if da := dict_array(doc, d, "Domain"); array_len(da) >= 4 {
|
|
dom: [4]f64
|
|
ok := true
|
|
for i in 0 ..< 4 {
|
|
dom[i] = array_num(doc, da, i)
|
|
if !is_finite(dom[i]) {
|
|
ok = false
|
|
}
|
|
}
|
|
// A reversed or non-finite /Domain is not worth failing the shading
|
|
// over; normalising it paints the same region the file meant.
|
|
if ok {
|
|
sh.domain = {
|
|
min(dom[0], dom[1]), max(dom[0], dom[1]),
|
|
min(dom[2], dom[3]), max(dom[2], dom[3]),
|
|
}
|
|
}
|
|
}
|
|
sh.mtx = dict_matrix(doc, d, "Matrix")
|
|
}
|
|
|
|
@(private)
|
|
shade_load_axial_radial :: proc(doc: ^Document, sh: ^Shading, d: ^Dict) -> Error {
|
|
need := sh.kind == .Axial ? 4 : 6
|
|
ca := dict_array(doc, d, "Coords")
|
|
if array_len(ca) < need {
|
|
return .Bad_Shading
|
|
}
|
|
for i in 0 ..< need {
|
|
sh.coords[i] = array_num(doc, ca, i)
|
|
if !is_finite(sh.coords[i]) {
|
|
return .Bad_Shading
|
|
}
|
|
}
|
|
|
|
sh.t0, sh.t1 = 0, 1
|
|
if da := dict_array(doc, d, "Domain"); array_len(da) >= 2 {
|
|
a := array_num(doc, da, 0)
|
|
b := array_num(doc, da, 1)
|
|
if is_finite(a) && is_finite(b) {
|
|
sh.t0, sh.t1 = a, b
|
|
}
|
|
}
|
|
|
|
if ea := dict_array(doc, d, "Extend"); array_len(ea) >= 2 {
|
|
sh.extend[0], _ = to_bool(array_get(doc, ea, 0))
|
|
sh.extend[1], _ = to_bool(array_get(doc, ea, 1))
|
|
}
|
|
return .None
|
|
}
|
|
|
|
@(private)
|
|
shade_load_mesh :: proc(doc: ^Document, sh: ^Shading, d: ^Dict) -> Error {
|
|
bpc := int(dict_int(doc, d, "BitsPerCoordinate", 0))
|
|
switch bpc {
|
|
case 1, 2, 4, 8, 12, 16, 24, 32:
|
|
sh.bits_per_coord = bpc
|
|
case:
|
|
return .Bad_Shading
|
|
}
|
|
|
|
bpcomp := int(dict_int(doc, d, "BitsPerComponent", 0))
|
|
switch bpcomp {
|
|
case 1, 2, 4, 8, 12, 16:
|
|
sh.bits_per_component = bpcomp
|
|
case:
|
|
return .Bad_Shading
|
|
}
|
|
|
|
if sh.kind != .Lattice_Triangle {
|
|
bpf := int(dict_int(doc, d, "BitsPerFlag", 0))
|
|
switch bpf {
|
|
case 2, 4, 8:
|
|
sh.bits_per_flag = bpf
|
|
case:
|
|
return .Bad_Shading
|
|
}
|
|
}
|
|
|
|
// With a /Function the stream carries one parametric value per vertex, not
|
|
// one value per colour component (8.7.4.5.5).
|
|
sh.n_comp = len(sh.funcs) > 0 ? 1 : sh.cs.n
|
|
need := 4 + 2 * sh.n_comp
|
|
da := dict_array(doc, d, "Decode")
|
|
if array_len(da) < need {
|
|
return .Bad_Shading
|
|
}
|
|
dec := make([]f64, need, doc.allocator)
|
|
for i in 0 ..< need {
|
|
dec[i] = array_num(doc, da, i)
|
|
if !is_finite(dec[i]) {
|
|
return .Bad_Shading
|
|
}
|
|
}
|
|
sh.decode = dec
|
|
|
|
if sh.kind == .Lattice_Triangle {
|
|
vpr := int(dict_int(doc, d, "VerticesPerRow", 0))
|
|
if vpr < 2 || vpr > MAX_MESH_ROW {
|
|
return .Bad_Shading
|
|
}
|
|
sh.vertices_per_row = vpr
|
|
}
|
|
return .None
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Colour
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// shade_components_to_rgb converts raw colour components -- the ones a mesh
|
|
// vertex carries when there is no /Function -- through the shading's space.
|
|
@(private)
|
|
shade_components_to_rgb :: proc(sh: ^Shading, comps: []f64) -> [3]f64 {
|
|
c := Color{n = sh.cs.n}
|
|
for i in 0 ..< min(sh.cs.n, len(comps)) {
|
|
c.v[i] = comps[i]
|
|
}
|
|
return colorspace_to_rgb(sh.cs, c)
|
|
}
|
|
|
|
// shade_eval runs the shading's function(s) at `in_` and converts the result
|
|
// to RGB. `in_` is one parametric value for types 2-7 and two coordinates for
|
|
// type 1.
|
|
@(private)
|
|
shade_eval :: proc(sh: ^Shading, in_: []f64) -> [3]f64 {
|
|
comps: [MAX_COLOR_COMPONENTS]f64
|
|
n := sh.cs.n
|
|
switch len(sh.funcs) {
|
|
case 0:
|
|
// Only reachable for a mesh whose colours come from the stream; the
|
|
// caller uses shade_components_to_rgb there.
|
|
return [3]f64{0, 0, 0}
|
|
case 1:
|
|
// One function supplying every component: the array spelling with a
|
|
// single entry means the same thing as the bare one, and both occur.
|
|
function_eval(sh.funcs[0], in_, comps[:n])
|
|
case:
|
|
one: [1]f64
|
|
for i in 0 ..< min(len(sh.funcs), n) {
|
|
// A nil slot is a sub-function that would not load. Its component
|
|
// stays zero; the ones around it keep their own indices.
|
|
if sh.funcs[i] == nil {
|
|
continue
|
|
}
|
|
function_eval(sh.funcs[i], in_, one[:])
|
|
comps[i] = one[0]
|
|
}
|
|
}
|
|
return shade_components_to_rgb(sh, comps[:n])
|
|
}
|
|
|
|
// Shade_Lut is the colour of an axial or radial shading sampled across its
|
|
// /Domain. Entry i is the colour at t0 + (i/(N-1)) * (t1 - t0).
|
|
@(private)
|
|
Shade_Lut :: struct {
|
|
rgb: [SHADE_LUT_SIZE][3]f64,
|
|
}
|
|
|
|
@(private)
|
|
shade_build_lut :: proc(sh: ^Shading, lut: ^Shade_Lut) {
|
|
in_: [1]f64
|
|
for i in 0 ..< SHADE_LUT_SIZE {
|
|
s := f64(i) / f64(SHADE_LUT_SIZE - 1)
|
|
in_[0] = sh.t0 + s * (sh.t1 - sh.t0)
|
|
lut.rgb[i] = shade_eval(sh, in_[:])
|
|
}
|
|
}
|
|
|
|
// shade_lut_at looks a parametric position 0..1 up, rounding to the nearest
|
|
// sample so the error is half a step rather than a whole one.
|
|
@(private)
|
|
shade_lut_at :: proc(lut: ^Shade_Lut, s: f64) -> [3]f64 {
|
|
if !(s > 0) {
|
|
return lut.rgb[0]
|
|
}
|
|
if s >= 1 {
|
|
return lut.rgb[SHADE_LUT_SIZE - 1]
|
|
}
|
|
i := int(s * f64(SHADE_LUT_SIZE - 1) + 0.5)
|
|
return lut.rgb[clamp(i, 0, SHADE_LUT_SIZE - 1)]
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Bounds
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// shading_bounds is the device-space region the shading can paint under `ctm`.
|
|
//
|
|
// RECT_INFINITE means "unbounded", which is the honest answer for an axial
|
|
// shading (the strip between its two ends is infinite across the axis, and an
|
|
// /Extend makes it infinite along it too) and for an extended radial one. It
|
|
// is the identity for `rect_intersect`, so a caller that intersects it with a
|
|
// clip gets the clip, which is exactly right.
|
|
shading_bounds :: proc(sh: ^Shading, ctm: Matrix) -> Rect {
|
|
if sh == nil || !matrix_is_finite(ctm) {
|
|
return RECT_EMPTY
|
|
}
|
|
|
|
r := RECT_INFINITE
|
|
switch sh.kind {
|
|
case .Function_Based:
|
|
dom := Rect{sh.domain[0], sh.domain[2], sh.domain[1], sh.domain[3]}
|
|
r = transform_rect(matrix_mul(sh.mtx, ctm), dom)
|
|
case .Axial:
|
|
// Unbounded across the axis whatever /Extend says.
|
|
r = RECT_INFINITE
|
|
case .Radial:
|
|
if !sh.extend[0] && !sh.extend[1] {
|
|
// Every circle between the two ends is a convex combination of a
|
|
// point of the first disc and a point of the second, so the two
|
|
// discs bound the whole family.
|
|
//
|
|
// The corners are taken with min and max rather than by unioning
|
|
// two rectangles: a circle of radius zero -- which is how every
|
|
// "glow" gradient starts -- is an empty rectangle, and
|
|
// `rect_union` drops an empty operand, which would shrink the
|
|
// bound to the other circle and clip the narrow end of the cone
|
|
// away.
|
|
rr0 := max(sh.coords[2], 0)
|
|
rr1 := max(sh.coords[5], 0)
|
|
r = transform_rect(ctm, Rect{
|
|
min(sh.coords[0] - rr0, sh.coords[3] - rr1),
|
|
min(sh.coords[1] - rr0, sh.coords[4] - rr1),
|
|
max(sh.coords[0] + rr0, sh.coords[3] + rr1),
|
|
max(sh.coords[1] + rr0, sh.coords[4] + rr1),
|
|
})
|
|
}
|
|
case .Free_Triangle, .Lattice_Triangle, .Coons, .Tensor:
|
|
// /Decode fixes the range every coordinate in the stream decodes into,
|
|
// so the mesh is bounded without reading a byte of it.
|
|
if len(sh.decode) >= 4 {
|
|
r = transform_rect(ctm, rect_normalize(
|
|
Rect{sh.decode[0], sh.decode[2], sh.decode[1], sh.decode[3]}))
|
|
}
|
|
}
|
|
|
|
if sh.has_bbox {
|
|
r = rect_intersect(r, transform_rect(ctm, sh.bbox))
|
|
}
|
|
if !rect_is_finite(r) {
|
|
return RECT_INFINITE
|
|
}
|
|
return r
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Painting
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
Shade_Ctx :: struct {
|
|
doc: ^Document,
|
|
sh: ^Shading,
|
|
bmp: ^Bitmap,
|
|
clip: Raster_Clip,
|
|
alpha: f64,
|
|
mode: Blend_Mode,
|
|
mask: ^Bitmap,
|
|
|
|
ctm: Matrix,
|
|
// Device space back to the shading's own space.
|
|
inv: Matrix,
|
|
|
|
// One scratch row of straight RGBA8, indexed from clip.x0. Alpha 0 marks a
|
|
// pixel the shading does not cover.
|
|
row: []u8,
|
|
// The mask's coverage for `mask_row_y`, one byte per pixel from clip.x0,
|
|
// or nil when there is no mask.
|
|
mask_row: []u8,
|
|
mask_row_y: int,
|
|
|
|
fuel: int,
|
|
tris: int,
|
|
}
|
|
|
|
// shading_paint composites `sh` onto `bmp` through `clip`.
|
|
//
|
|
// ctm maps the shading's own space to device space
|
|
// clip device pixels, half-open, intersected with the bitmap here
|
|
// alpha the constant alpha of the paint operation
|
|
// mask an optional soft mask aligned with `bmp` in device pixels, one
|
|
// component read (what `mask_from_bitmap` produces). Pixels outside
|
|
// it read as 255, so a mask smaller than the clip shows content
|
|
// rather than erasing it.
|
|
//
|
|
// `bmp` is PREMULTIPLIED, like everything below the Device line. A singular or
|
|
// non-finite CTM paints nothing, and so does a colour space of /None.
|
|
// `context.allocator` supplies scratch only; everything it takes is released
|
|
// before this returns.
|
|
shading_paint :: proc(doc: ^Document, sh: ^Shading, bmp: ^Bitmap, ctm: Matrix,
|
|
clip: Raster_Clip, alpha: f64, mode: Blend_Mode, mask: ^Bitmap) -> Error {
|
|
if sh == nil || bmp == nil || sh.cs == nil {
|
|
return .Invalid_Argument
|
|
}
|
|
// A /None separation marks nothing at all on the page (8.6.6.4).
|
|
if sh.cs.is_none {
|
|
return .None
|
|
}
|
|
if !matrix_is_finite(ctm) {
|
|
return .None
|
|
}
|
|
inv, invertible := matrix_invert(ctm)
|
|
if !invertible {
|
|
return .None
|
|
}
|
|
|
|
ga := blend_clamp01(alpha)
|
|
if ga <= 0 {
|
|
return .None
|
|
}
|
|
|
|
cl := Raster_Clip{
|
|
x0 = max(clip.x0, 0),
|
|
y0 = max(clip.y0, 0),
|
|
x1 = min(clip.x1, bmp.width),
|
|
y1 = min(clip.y1, bmp.height),
|
|
}
|
|
if cl.x1 <= cl.x0 || cl.y1 <= cl.y0 {
|
|
return .None
|
|
}
|
|
|
|
// Narrowing to what the shading can possibly reach keeps a small gradient
|
|
// on a big page from walking the whole page, and is what applies /BBox to
|
|
// a mesh.
|
|
b := shading_bounds(sh, ctm)
|
|
cl = Raster_Clip{
|
|
x0 = raster_floor_clamped(b.x0, cl.x0, cl.x1),
|
|
y0 = raster_floor_clamped(b.y0, cl.y0, cl.y1),
|
|
x1 = raster_ceil_clamped(b.x1, cl.x0, cl.x1),
|
|
y1 = raster_ceil_clamped(b.y1, cl.y0, cl.y1),
|
|
}
|
|
if cl.x1 <= cl.x0 || cl.y1 <= cl.y0 {
|
|
return .None
|
|
}
|
|
|
|
width := cl.x1 - cl.x0
|
|
row, row_err := make([]u8, width * 4, context.allocator)
|
|
if row_err != nil {
|
|
return .Out_Of_Memory
|
|
}
|
|
defer delete(row, context.allocator)
|
|
|
|
ctx := Shade_Ctx{
|
|
doc = doc,
|
|
sh = sh,
|
|
bmp = bmp,
|
|
clip = cl,
|
|
alpha = ga,
|
|
mode = mode,
|
|
mask = mask,
|
|
ctm = ctm,
|
|
inv = inv,
|
|
row = row,
|
|
mask_row_y = -1,
|
|
fuel = SHADE_PIXEL_FUEL,
|
|
}
|
|
|
|
if mask != nil {
|
|
mrow, mask_err := make([]u8, width, context.allocator)
|
|
if mask_err != nil {
|
|
return .Out_Of_Memory
|
|
}
|
|
ctx.mask_row = mrow
|
|
}
|
|
// Freed here rather than in the branch above: a `defer` runs at the end of
|
|
// the block it sits in, so one inside the `if` would free the row before a
|
|
// single span had been composited through it.
|
|
defer if ctx.mask_row != nil { delete(ctx.mask_row, context.allocator) }
|
|
|
|
switch sh.kind {
|
|
case .Function_Based:
|
|
shade_paint_function(&ctx)
|
|
case .Axial:
|
|
shade_paint_axial(&ctx)
|
|
case .Radial:
|
|
shade_paint_radial(&ctx)
|
|
case .Free_Triangle, .Lattice_Triangle, .Coons, .Tensor:
|
|
return shade_paint_mesh(&ctx)
|
|
}
|
|
return .None
|
|
}
|
|
|
|
// shade_put writes one covered pixel into the scratch row.
|
|
@(private)
|
|
shade_put :: proc(ctx: ^Shade_Ctx, x: int, rgb: [3]f64) {
|
|
i := (x - ctx.clip.x0) * 4
|
|
if i < 0 || i + 4 > len(ctx.row) {
|
|
return
|
|
}
|
|
ctx.row[i] = blend_quantize(rgb[0])
|
|
ctx.row[i + 1] = blend_quantize(rgb[1])
|
|
ctx.row[i + 2] = blend_quantize(rgb[2])
|
|
ctx.row[i + 3] = 255
|
|
}
|
|
|
|
@(private)
|
|
shade_skip :: proc(ctx: ^Shade_Ctx, x: int) {
|
|
i := (x - ctx.clip.x0) * 4
|
|
if i < 0 || i + 4 > len(ctx.row) {
|
|
return
|
|
}
|
|
ctx.row[i + 3] = 0
|
|
}
|
|
|
|
// shade_mask_row lifts the mask's coverage for scanline `y` into a byte per
|
|
// pixel, which is the form `blend_span_rgba8` indexes.
|
|
@(private)
|
|
shade_mask_row :: proc(ctx: ^Shade_Ctx, y: int) {
|
|
if ctx.mask_row == nil || ctx.mask_row_y == y {
|
|
return
|
|
}
|
|
ctx.mask_row_y = y
|
|
for i in 0 ..< len(ctx.mask_row) {
|
|
ctx.mask_row[i] = 255
|
|
}
|
|
if ctx.mask == nil {
|
|
return
|
|
}
|
|
src := bitmap_row(ctx.mask, y)
|
|
if src == nil {
|
|
return
|
|
}
|
|
n := ctx.mask.n
|
|
for i in 0 ..< len(ctx.mask_row) {
|
|
x := ctx.clip.x0 + i
|
|
if x >= ctx.mask.width {
|
|
break
|
|
}
|
|
off := x * n
|
|
if off < len(src) {
|
|
ctx.mask_row[i] = src[off]
|
|
}
|
|
}
|
|
}
|
|
|
|
// shade_flush composites the covered runs of `row` between x_from and x_to.
|
|
@(private)
|
|
shade_flush :: proc(ctx: ^Shade_Ctx, y, x_from, x_to: int) {
|
|
lo := max(x_from, ctx.clip.x0)
|
|
hi := min(x_to, ctx.clip.x1)
|
|
if lo >= hi {
|
|
return
|
|
}
|
|
shade_mask_row(ctx, y)
|
|
|
|
i := lo
|
|
for i < hi {
|
|
if ctx.row[(i - ctx.clip.x0) * 4 + 3] == 0 {
|
|
i += 1
|
|
continue
|
|
}
|
|
j := i
|
|
for j < hi && ctx.row[(j - ctx.clip.x0) * 4 + 3] != 0 {
|
|
j += 1
|
|
}
|
|
a := (i - ctx.clip.x0) * 4
|
|
b := (j - ctx.clip.x0) * 4
|
|
mrow: []u8
|
|
if ctx.mask_row != nil {
|
|
mrow = ctx.mask_row[i - ctx.clip.x0 : j - ctx.clip.x0]
|
|
}
|
|
blend_span_rgba8(ctx.bmp, y, i, ctx.row[a:b], ctx.alpha, ctx.mode, nil, mrow)
|
|
i = j
|
|
}
|
|
}
|
|
|
|
// shade_in_bbox tests a device pixel centre against /BBox, which is given in
|
|
// the shading's own space. Testing there rather than against the transformed
|
|
// box keeps a rotated CTM from painting the corners the box excludes.
|
|
@(private)
|
|
shade_in_bbox :: proc(ctx: ^Shade_Ctx, p: Point) -> bool {
|
|
if !ctx.sh.has_bbox {
|
|
return true
|
|
}
|
|
return rect_contains(ctx.sh.bbox, p)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type 1: function-based
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
shade_paint_function :: proc(ctx: ^Shade_Ctx) {
|
|
sh := ctx.sh
|
|
// /Matrix maps the domain into the shading's space, which the CTM then
|
|
// maps to the device; one inverse takes a pixel all the way back.
|
|
full, ok := matrix_invert(matrix_mul(sh.mtx, ctx.ctm))
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
in_: [2]f64
|
|
for y in ctx.clip.y0 ..< ctx.clip.y1 {
|
|
for x in ctx.clip.x0 ..< ctx.clip.x1 {
|
|
dev := Point{f64(x) + 0.5, f64(y) + 0.5}
|
|
p := transform_point(full, dev)
|
|
if !is_finite(p.x) || !is_finite(p.y) ||
|
|
p.x < sh.domain[0] || p.x > sh.domain[1] ||
|
|
p.y < sh.domain[2] || p.y > sh.domain[3] {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
if sh.has_bbox && !shade_in_bbox(ctx, transform_point(ctx.inv, dev)) {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
in_[0] = p.x
|
|
in_[1] = p.y
|
|
shade_put(ctx, x, shade_eval(sh, in_[:]))
|
|
}
|
|
shade_flush(ctx, y, ctx.clip.x0, ctx.clip.x1)
|
|
ctx.fuel -= ctx.clip.x1 - ctx.clip.x0
|
|
if ctx.fuel <= 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type 2: axial
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
shade_paint_axial :: proc(ctx: ^Shade_Ctx) {
|
|
sh := ctx.sh
|
|
p0 := Point{sh.coords[0], sh.coords[1]}
|
|
d := Point{sh.coords[2] - p0.x, sh.coords[3] - p0.y}
|
|
len2 := d.x * d.x + d.y * d.y
|
|
// A zero-length axis leaves the parametric variable undefined; there is no
|
|
// colour to choose, so nothing is painted.
|
|
if !(len2 > 0) {
|
|
return
|
|
}
|
|
inv_len2 := 1 / len2
|
|
|
|
lut: Shade_Lut
|
|
shade_build_lut(sh, &lut)
|
|
|
|
for y in ctx.clip.y0 ..< ctx.clip.y1 {
|
|
for x in ctx.clip.x0 ..< ctx.clip.x1 {
|
|
p := transform_point(ctx.inv, Point{f64(x) + 0.5, f64(y) + 0.5})
|
|
s := ((p.x - p0.x) * d.x + (p.y - p0.y) * d.y) * inv_len2
|
|
if !is_finite(s) || !shade_in_bbox(ctx, p) {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
if s < 0 {
|
|
if !sh.extend[0] {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
s = 0
|
|
} else if s > 1 {
|
|
if !sh.extend[1] {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
s = 1
|
|
}
|
|
shade_put(ctx, x, shade_lut_at(&lut, s))
|
|
}
|
|
shade_flush(ctx, y, ctx.clip.x0, ctx.clip.x1)
|
|
ctx.fuel -= ctx.clip.x1 - ctx.clip.x0
|
|
if ctx.fuel <= 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type 3: radial
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// shade_radial_accept tests one candidate s against the two rules that decide
|
|
// whether a circle in the family exists there at all: its radius must not be
|
|
// negative (8.7.4.5.4 -- the extension stops where the circles would invert),
|
|
// and a value outside 0..1 needs the matching /Extend. The returned value is
|
|
// clamped, which is what makes an extended shading paint its end colour.
|
|
@(private)
|
|
shade_radial_accept :: proc(s, r0, dr: f64, extend: [2]bool) -> (f64, bool) {
|
|
if !is_finite(s) {
|
|
return 0, false
|
|
}
|
|
if r0 + s * dr < 0 {
|
|
return 0, false
|
|
}
|
|
if s < 0 {
|
|
return 0, extend[0]
|
|
}
|
|
if s > 1 {
|
|
return 1, extend[1]
|
|
}
|
|
return s, true
|
|
}
|
|
|
|
// shade_radial_solve finds the largest s whose circle passes through the point,
|
|
// as the format requires: later circles paint over earlier ones, so the largest
|
|
// s is the one that shows.
|
|
//
|
|
// a = dx^2 + dy^2 - dr^2
|
|
// b = px*dx + py*dy + r0*dr
|
|
// c = px^2 + py^2 - r0^2
|
|
//
|
|
// solving a*s^2 - 2*b*s + c = 0. `a` is zero exactly when the circles' radii
|
|
// grow as fast as their centres move -- the cone degenerates to a paraboloid
|
|
// and the equation is linear. A negative discriminant means no circle in the
|
|
// family reaches the point, which must paint nothing rather than an end colour.
|
|
@(private)
|
|
shade_radial_solve :: proc(a, b, c, r0, dr: f64, extend: [2]bool) -> (f64, bool) {
|
|
if a == 0 {
|
|
if b == 0 {
|
|
return 0, false
|
|
}
|
|
return shade_radial_accept(c / (2 * b), r0, dr, extend)
|
|
}
|
|
disc := b * b - a * c
|
|
if !(disc >= 0) {
|
|
return 0, false
|
|
}
|
|
sq := math.sqrt(disc)
|
|
s1 := (b + sq) / a
|
|
s2 := (b - sq) / a
|
|
hi := max(s1, s2)
|
|
lo := min(s1, s2)
|
|
if s, ok := shade_radial_accept(hi, r0, dr, extend); ok {
|
|
return s, true
|
|
}
|
|
return shade_radial_accept(lo, r0, dr, extend)
|
|
}
|
|
|
|
@(private)
|
|
shade_paint_radial :: proc(ctx: ^Shade_Ctx) {
|
|
sh := ctx.sh
|
|
x0, y0, r0 := sh.coords[0], sh.coords[1], sh.coords[2]
|
|
dx := sh.coords[3] - x0
|
|
dy := sh.coords[4] - y0
|
|
dr := sh.coords[5] - r0
|
|
a := dx * dx + dy * dy - dr * dr
|
|
|
|
lut: Shade_Lut
|
|
shade_build_lut(sh, &lut)
|
|
|
|
for y in ctx.clip.y0 ..< ctx.clip.y1 {
|
|
for x in ctx.clip.x0 ..< ctx.clip.x1 {
|
|
p := transform_point(ctx.inv, Point{f64(x) + 0.5, f64(y) + 0.5})
|
|
if !is_finite(p.x) || !is_finite(p.y) || !shade_in_bbox(ctx, p) {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
px := p.x - x0
|
|
py := p.y - y0
|
|
b := px * dx + py * dy + r0 * dr
|
|
c := px * px + py * py - r0 * r0
|
|
s, ok := shade_radial_solve(a, b, c, r0, dr, sh.extend)
|
|
if !ok {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
shade_put(ctx, x, shade_lut_at(&lut, s))
|
|
}
|
|
shade_flush(ctx, y, ctx.clip.x0, ctx.clip.x1)
|
|
ctx.fuel -= ctx.clip.x1 - ctx.clip.x0
|
|
if ctx.fuel <= 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Triangles
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
Shade_Vertex :: struct {
|
|
// Device space: the mesh is transformed as it is read, so the whole
|
|
// rasteriser works in one space.
|
|
p: Point,
|
|
rgb: [3]f64,
|
|
}
|
|
|
|
// shade_top_left reports whether an edge owns the pixel centres that land
|
|
// exactly on it. With triangles wound to a positive area, the interior lies on
|
|
// the e >= 0 side, which makes an edge running upwards a left edge and a
|
|
// horizontal edge running right a top edge. Two triangles sharing an edge
|
|
// traverse it in opposite directions, so exactly one of them owns it.
|
|
@(private)
|
|
shade_top_left :: proc "contextless" (d: Point) -> bool {
|
|
return d.y < 0 || (d.y == 0 && d.x > 0)
|
|
}
|
|
|
|
@(private)
|
|
shade_edge_accept :: proc "contextless" (w: f64, top_left: bool) -> bool {
|
|
// Written so a NaN is outside rather than inside.
|
|
return w > 0 || (w == 0 && top_left)
|
|
}
|
|
|
|
// shade_triangle scan-converts one Gouraud-shaded triangle at pixel centres.
|
|
@(private)
|
|
shade_triangle :: proc(ctx: ^Shade_Ctx, va, vb, vc: Shade_Vertex) {
|
|
a := va
|
|
b := vb
|
|
c := vc
|
|
if !is_finite(a.p.x) || !is_finite(a.p.y) ||
|
|
!is_finite(b.p.x) || !is_finite(b.p.y) ||
|
|
!is_finite(c.p.x) || !is_finite(c.p.y) {
|
|
return
|
|
}
|
|
|
|
area := (b.p.x - a.p.x) * (c.p.y - a.p.y) - (b.p.y - a.p.y) * (c.p.x - a.p.x)
|
|
if !is_finite(area) || area == 0 {
|
|
return
|
|
}
|
|
if area < 0 {
|
|
// Winding every triangle the same way is what makes the top-left rule
|
|
// consistent between neighbours: an adjacent triangle on the far side
|
|
// of a shared edge necessarily traverses that edge the other way.
|
|
b, c = c, b
|
|
area = -area
|
|
}
|
|
ctx.tris += 1
|
|
|
|
bx0 := raster_floor_clamped(min(a.p.x, b.p.x, c.p.x), ctx.clip.x0, ctx.clip.x1)
|
|
bx1 := raster_ceil_clamped(max(a.p.x, b.p.x, c.p.x), ctx.clip.x0, ctx.clip.x1)
|
|
by0 := raster_floor_clamped(min(a.p.y, b.p.y, c.p.y), ctx.clip.y0, ctx.clip.y1)
|
|
by1 := raster_ceil_clamped(max(a.p.y, b.p.y, c.p.y), ctx.clip.y0, ctx.clip.y1)
|
|
if bx1 <= bx0 || by1 <= by0 {
|
|
return
|
|
}
|
|
|
|
d0 := Point{b.p.x - a.p.x, b.p.y - a.p.y}
|
|
d1 := Point{c.p.x - b.p.x, c.p.y - b.p.y}
|
|
d2 := Point{a.p.x - c.p.x, a.p.y - c.p.y}
|
|
tl0 := shade_top_left(d0)
|
|
tl1 := shade_top_left(d1)
|
|
tl2 := shade_top_left(d2)
|
|
inv_area := 1 / area
|
|
|
|
for y in by0 ..< by1 {
|
|
py := f64(y) + 0.5
|
|
for x in bx0 ..< bx1 {
|
|
px := f64(x) + 0.5
|
|
// Recomputed rather than stepped along the row on purpose: the
|
|
// same edge evaluated from two neighbouring triangles must give
|
|
// bit-identical results, and an incremental sum would depend on
|
|
// where each triangle's bounding box started.
|
|
w0 := d0.x * (py - a.p.y) - d0.y * (px - a.p.x)
|
|
w1 := d1.x * (py - b.p.y) - d1.y * (px - b.p.x)
|
|
w2 := d2.x * (py - c.p.y) - d2.y * (px - c.p.x)
|
|
if !shade_edge_accept(w0, tl0) || !shade_edge_accept(w1, tl1) ||
|
|
!shade_edge_accept(w2, tl2) {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
if ctx.sh.has_bbox && !shade_in_bbox(ctx, transform_point(ctx.inv, Point{px, py})) {
|
|
shade_skip(ctx, x)
|
|
continue
|
|
}
|
|
// w1 is the edge opposite a, w2 opposite b, w0 opposite c.
|
|
la := w1 * inv_area
|
|
lb := w2 * inv_area
|
|
lc := w0 * inv_area
|
|
rgb := [3]f64{
|
|
la * a.rgb[0] + lb * b.rgb[0] + lc * c.rgb[0],
|
|
la * a.rgb[1] + lb * b.rgb[1] + lc * c.rgb[1],
|
|
la * a.rgb[2] + lb * b.rgb[2] + lc * c.rgb[2],
|
|
}
|
|
shade_put(ctx, x, rgb)
|
|
}
|
|
shade_flush(ctx, y, bx0, bx1)
|
|
ctx.fuel -= bx1 - bx0
|
|
if ctx.fuel <= 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mesh streams
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
Mesh_Reader :: struct {
|
|
sh: ^Shading,
|
|
ctm: Matrix,
|
|
data: []byte,
|
|
// Bit position, MSB first, which is how every packed field in a PDF is
|
|
// written.
|
|
pos: int,
|
|
max_coord: f64,
|
|
max_comp: f64,
|
|
count: int,
|
|
}
|
|
|
|
@(private)
|
|
mesh_reader_make :: proc(sh: ^Shading, ctm: Matrix, data: []byte) -> Mesh_Reader {
|
|
return Mesh_Reader{
|
|
sh = sh,
|
|
ctm = ctm,
|
|
data = data,
|
|
max_coord = math.pow(2, f64(sh.bits_per_coord)) - 1,
|
|
max_comp = math.pow(2, f64(sh.bits_per_component)) - 1,
|
|
}
|
|
}
|
|
|
|
// mesh_bits reads the next `n` bits. Returns ok = false at end of data, and
|
|
// leaves the reader at the end so every later read fails too -- a truncated
|
|
// mesh stops, it does not wrap around and paint garbage.
|
|
@(private)
|
|
mesh_bits :: proc(r: ^Mesh_Reader, n: int) -> (v: u32, ok: bool) {
|
|
total := len(r.data) * 8
|
|
if n <= 0 || n > 32 || r.pos + n > total {
|
|
r.pos = total
|
|
return 0, false
|
|
}
|
|
acc: u64
|
|
left := n
|
|
for left > 0 {
|
|
byte_index := r.pos >> 3
|
|
bit_off := r.pos & 7
|
|
avail := 8 - bit_off
|
|
take := min(avail, left)
|
|
chunk := (u64(r.data[byte_index]) >> uint(avail - take)) & u64((1 << uint(take)) - 1)
|
|
acc = acc << uint(take) | chunk
|
|
r.pos += take
|
|
left -= take
|
|
}
|
|
return u32(acc), true
|
|
}
|
|
|
|
// mesh_align skips to the next byte boundary. Free-form triangles and patches
|
|
// pad each record to a whole number of bytes; lattice rows do not.
|
|
@(private)
|
|
mesh_align :: proc(r: ^Mesh_Reader) {
|
|
r.pos = ((r.pos + 7) / 8) * 8
|
|
}
|
|
|
|
@(private)
|
|
mesh_decode :: proc(v: u32, maxv, lo, hi: f64) -> f64 {
|
|
if !(maxv > 0) {
|
|
return lo
|
|
}
|
|
return lo + f64(v) * (hi - lo) / maxv
|
|
}
|
|
|
|
@(private)
|
|
mesh_point :: proc(r: ^Mesh_Reader) -> (Point, bool) {
|
|
xv, ok0 := mesh_bits(r, r.sh.bits_per_coord)
|
|
yv, ok1 := mesh_bits(r, r.sh.bits_per_coord)
|
|
if !ok0 || !ok1 {
|
|
return Point{}, false
|
|
}
|
|
dec := r.sh.decode
|
|
p := Point{
|
|
mesh_decode(xv, r.max_coord, dec[0], dec[1]),
|
|
mesh_decode(yv, r.max_coord, dec[2], dec[3]),
|
|
}
|
|
return transform_point(r.ctm, p), true
|
|
}
|
|
|
|
@(private)
|
|
mesh_color :: proc(r: ^Mesh_Reader) -> ([3]f64, bool) {
|
|
comps: [MAX_COLOR_COMPONENTS]f64
|
|
sh := r.sh
|
|
dec := sh.decode
|
|
for i in 0 ..< sh.n_comp {
|
|
cv, ok := mesh_bits(r, sh.bits_per_component)
|
|
if !ok {
|
|
return [3]f64{}, false
|
|
}
|
|
comps[i] = mesh_decode(cv, r.max_comp, dec[4 + 2 * i], dec[5 + 2 * i])
|
|
}
|
|
if len(sh.funcs) > 0 {
|
|
return shade_eval(sh, comps[:1]), true
|
|
}
|
|
return shade_components_to_rgb(sh, comps[:sh.n_comp]), true
|
|
}
|
|
|
|
@(private)
|
|
mesh_vertex :: proc(r: ^Mesh_Reader, out: ^Shade_Vertex) -> bool {
|
|
if r.count >= MAX_MESH_VERTICES {
|
|
return false
|
|
}
|
|
p, p_ok := mesh_point(r)
|
|
if !p_ok {
|
|
return false
|
|
}
|
|
rgb, c_ok := mesh_color(r)
|
|
if !c_ok {
|
|
return false
|
|
}
|
|
out.p = p
|
|
out.rgb = rgb
|
|
r.count += 1
|
|
return true
|
|
}
|
|
|
|
@(private)
|
|
shade_paint_mesh :: proc(ctx: ^Shade_Ctx) -> Error {
|
|
sh := ctx.sh
|
|
if sh.stream == nil || len(sh.decode) < 4 + 2 * sh.n_comp {
|
|
return .None
|
|
}
|
|
data, err := stream_data(ctx.doc, sh.stream)
|
|
if err != .None || len(data) == 0 {
|
|
return .None
|
|
}
|
|
|
|
r := mesh_reader_make(sh, ctx.ctm, data)
|
|
switch sh.kind {
|
|
case .Free_Triangle:
|
|
shade_mesh_free(ctx, &r)
|
|
case .Lattice_Triangle:
|
|
return shade_mesh_lattice(ctx, &r)
|
|
case .Coons, .Tensor:
|
|
shade_mesh_patches(ctx, &r)
|
|
case .Function_Based, .Axial, .Radial:
|
|
// Not reachable: shading_paint dispatches those elsewhere.
|
|
}
|
|
return .None
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type 4: free-form triangles
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
mesh_flagged_vertex :: proc(r: ^Mesh_Reader, out: ^Shade_Vertex) -> bool {
|
|
// The two vertices that follow a flag-0 vertex carry a flag of their own,
|
|
// which the format says shall be 0 and which nothing reads.
|
|
if _, ok := mesh_bits(r, r.sh.bits_per_flag); !ok {
|
|
return false
|
|
}
|
|
if !mesh_vertex(r, out) {
|
|
return false
|
|
}
|
|
mesh_align(r)
|
|
return true
|
|
}
|
|
|
|
@(private)
|
|
shade_mesh_free :: proc(ctx: ^Shade_Ctx, r: ^Mesh_Reader) {
|
|
va, vb, vc: Shade_Vertex
|
|
have := false
|
|
|
|
for ctx.tris < MAX_MESH_TRIANGLES && ctx.fuel > 0 {
|
|
flag, flag_ok := mesh_bits(r, r.sh.bits_per_flag)
|
|
if !flag_ok {
|
|
return
|
|
}
|
|
v: Shade_Vertex
|
|
if !mesh_vertex(r, &v) {
|
|
return
|
|
}
|
|
mesh_align(r)
|
|
|
|
switch {
|
|
case flag == 0 || !have:
|
|
// A continuation flag with no previous triangle is malformed;
|
|
// starting a fresh triangle is the recovery that loses least.
|
|
v1, v2: Shade_Vertex
|
|
if !mesh_flagged_vertex(r, &v1) || !mesh_flagged_vertex(r, &v2) {
|
|
return
|
|
}
|
|
va, vb, vc = v, v1, v2
|
|
case flag == 1:
|
|
va, vb, vc = vb, vc, v
|
|
case flag == 2:
|
|
va, vb, vc = va, vc, v
|
|
case:
|
|
add_repair(ctx.doc, .Bad_Resource, 0)
|
|
return
|
|
}
|
|
shade_triangle(ctx, va, vb, vc)
|
|
have = true
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type 5: lattice-form triangles
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private)
|
|
shade_mesh_lattice :: proc(ctx: ^Shade_Ctx, r: ^Mesh_Reader) -> Error {
|
|
per_row := ctx.sh.vertices_per_row
|
|
if per_row < 2 || per_row > MAX_MESH_ROW {
|
|
return .None
|
|
}
|
|
|
|
rows, rows_err := make([]Shade_Vertex, per_row * 2, context.allocator)
|
|
if rows_err != nil {
|
|
return .Out_Of_Memory
|
|
}
|
|
defer delete(rows, context.allocator)
|
|
prev := rows[:per_row]
|
|
cur := rows[per_row:]
|
|
|
|
have_prev := false
|
|
for ctx.tris < MAX_MESH_TRIANGLES && ctx.fuel > 0 {
|
|
complete := true
|
|
for i in 0 ..< per_row {
|
|
if !mesh_vertex(r, &cur[i]) {
|
|
complete = false
|
|
break
|
|
}
|
|
}
|
|
// A row that ran out mid-way is dropped whole: half a row has no
|
|
// triangles in it.
|
|
if !complete {
|
|
return .None
|
|
}
|
|
if have_prev {
|
|
for i in 0 ..< per_row - 1 {
|
|
shade_triangle(ctx, prev[i], prev[i + 1], cur[i])
|
|
shade_triangle(ctx, prev[i + 1], cur[i + 1], cur[i])
|
|
}
|
|
}
|
|
prev, cur = cur, prev
|
|
have_prev = true
|
|
}
|
|
return .None
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types 6 and 7: Coons and tensor patches
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// The twelve boundary control points are written anticlockwise from one
|
|
// corner, which lays them onto the 4x4 tensor grid like this (ISO 32000-2,
|
|
// figures 46 and 47):
|
|
//
|
|
// p1 p2 p3 p4 [0][0] [0][1] [0][2] [0][3]
|
|
// p12 p5 [1][0] p13 p14 [1][3]
|
|
// p11 p6 [2][0] p16 p15 [2][3]
|
|
// p10 p9 p8 p7 [3][0] [3][1] [3][2] [3][3]
|
|
//
|
|
// with the corner colours c1..c4 at p1, p4, p7 and p10 in that order.
|
|
@(private)
|
|
PATCH_GRID := [12][2]int{
|
|
{0, 0}, {0, 1}, {0, 2}, {0, 3},
|
|
{1, 3}, {2, 3}, {3, 3},
|
|
{3, 2}, {3, 1}, {3, 0},
|
|
{2, 0}, {1, 0},
|
|
}
|
|
|
|
// PATCH_SHARED tells which of the previous patch's points and colours a flag
|
|
// of 1, 2 or 3 reuses as the new patch's first edge (Table 85).
|
|
@(private)
|
|
PATCH_SHARED := [4][4]int{
|
|
{0, 0, 0, 0}, // flag 0: nothing is shared
|
|
{3, 4, 5, 6},
|
|
{6, 7, 8, 9},
|
|
{9, 10, 11, 0},
|
|
}
|
|
|
|
@(private)
|
|
PATCH_SHARED_COLOR := [4][2]int{
|
|
{0, 0},
|
|
{1, 2},
|
|
{2, 3},
|
|
{3, 0},
|
|
}
|
|
|
|
@(private)
|
|
shade_mesh_patches :: proc(ctx: ^Shade_Ctx, r: ^Mesh_Reader) {
|
|
n_points := ctx.sh.kind == .Tensor ? 16 : 12
|
|
|
|
pts, prev_pts: [16]Point
|
|
cols, prev_cols: [4][3]f64
|
|
have := false
|
|
|
|
for _ in 0 ..< MAX_MESH_PATCHES {
|
|
if ctx.tris >= MAX_MESH_TRIANGLES || ctx.fuel <= 0 {
|
|
return
|
|
}
|
|
|
|
raw_flag, flag_ok := mesh_bits(r, r.sh.bits_per_flag)
|
|
if !flag_ok {
|
|
return
|
|
}
|
|
flag := int(raw_flag)
|
|
if flag > 3 {
|
|
add_repair(ctx.doc, .Bad_Resource, 0)
|
|
return
|
|
}
|
|
|
|
first_point := 0
|
|
first_color := 0
|
|
if flag != 0 && have {
|
|
share := PATCH_SHARED[flag]
|
|
for k in 0 ..< 4 {
|
|
pts[k] = prev_pts[share[k]]
|
|
}
|
|
cshare := PATCH_SHARED_COLOR[flag]
|
|
cols[0] = prev_cols[cshare[0]]
|
|
cols[1] = prev_cols[cshare[1]]
|
|
first_point = 4
|
|
first_color = 2
|
|
}
|
|
|
|
for k in first_point ..< n_points {
|
|
p, ok := mesh_point(r)
|
|
if !ok {
|
|
return
|
|
}
|
|
pts[k] = p
|
|
}
|
|
for k in first_color ..< 4 {
|
|
c, ok := mesh_color(r)
|
|
if !ok {
|
|
return
|
|
}
|
|
cols[k] = c
|
|
}
|
|
mesh_align(r)
|
|
|
|
shade_patch(ctx, pts, cols, ctx.sh.kind == .Tensor)
|
|
prev_pts = pts
|
|
prev_cols = cols
|
|
have = true
|
|
}
|
|
}
|
|
|
|
@(private)
|
|
bernstein3 :: proc "contextless" (t: f64) -> [4]f64 {
|
|
mt := 1 - t
|
|
return [4]f64{mt * mt * mt, 3 * mt * mt * t, 3 * mt * t * t, t * t * t}
|
|
}
|
|
|
|
@(private)
|
|
patch_eval :: proc(pole: ^[4][4]Point, u, v: f64) -> (p: Point) {
|
|
bu := bernstein3(u)
|
|
bv := bernstein3(v)
|
|
for i in 0 ..< 4 {
|
|
for j in 0 ..< 4 {
|
|
w := bu[i] * bv[j]
|
|
p.x += w * pole[i][j].x
|
|
p.y += w * pole[i][j].y
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// patch_color is the bilinear interpolation of the four corner colours the
|
|
// format prescribes (8.7.4.5.7): c1 at (0,0), c2 at (0,1), c3 at (1,1) and
|
|
// c4 at (1,0), matching the corners the control points put there.
|
|
@(private)
|
|
patch_color :: proc(c: [4][3]f64, u, v: f64) -> (out: [3]f64) {
|
|
for i in 0 ..< 3 {
|
|
e0 := (1 - v) * c[0][i] + v * c[1][i]
|
|
e1 := (1 - v) * c[3][i] + v * c[2][i]
|
|
out[i] = (1 - u) * e0 + u * e1
|
|
}
|
|
return
|
|
}
|
|
|
|
// shade_patch subdivides one patch into triangles.
|
|
//
|
|
// The subdivision is uniform, with its step chosen from the patch's device
|
|
// size so that a step is about PATCH_STEP_PIXELS across: a patch a few pixels
|
|
// wide costs two triangles, and one covering a page is split as far as the cap
|
|
// allows. Uniform subdivision rather than adaptive flatness testing is a
|
|
// deliberate trade -- a Bezier patch's colour varies over its whole area, not
|
|
// just where it curves, so the flat parts need the same subdivision as the
|
|
// curved ones and adaptivity would buy nothing.
|
|
@(private)
|
|
shade_patch :: proc(ctx: ^Shade_Ctx, v: [16]Point, c: [4][3]f64, tensor: bool) {
|
|
pole: [4][4]Point
|
|
for k in 0 ..< 12 {
|
|
g := PATCH_GRID[k]
|
|
pole[g[0]][g[1]] = v[k]
|
|
}
|
|
if tensor {
|
|
pole[1][1] = v[12]
|
|
pole[1][2] = v[13]
|
|
pole[2][2] = v[14]
|
|
pole[2][1] = v[15]
|
|
} else {
|
|
// A Coons patch gives no interior control points; the format defines
|
|
// them from the boundary (8.7.4.5.7).
|
|
pole[1][1] = shade_coons_inner(pole[0][0], pole[0][1], pole[1][0], pole[0][3], pole[3][0], pole[3][1], pole[1][3], pole[3][3])
|
|
pole[1][2] = shade_coons_inner(pole[0][3], pole[0][2], pole[1][3], pole[0][0], pole[3][3], pole[3][2], pole[1][0], pole[3][0])
|
|
pole[2][1] = shade_coons_inner(pole[3][0], pole[3][1], pole[2][0], pole[3][3], pole[0][0], pole[0][1], pole[2][3], pole[0][3])
|
|
pole[2][2] = shade_coons_inner(pole[3][3], pole[3][2], pole[2][3], pole[3][0], pole[0][3], pole[0][2], pole[2][0], pole[0][0])
|
|
}
|
|
|
|
lo := pole[0][0]
|
|
hi := pole[0][0]
|
|
for i in 0 ..< 4 {
|
|
for j in 0 ..< 4 {
|
|
p := pole[i][j]
|
|
if !is_finite(p.x) || !is_finite(p.y) {
|
|
return
|
|
}
|
|
lo.x = min(lo.x, p.x); lo.y = min(lo.y, p.y)
|
|
hi.x = max(hi.x, p.x); hi.y = max(hi.y, p.y)
|
|
}
|
|
}
|
|
extent := max(hi.x - lo.x, hi.y - lo.y)
|
|
n := 1
|
|
if is_finite(extent) && extent > 0 {
|
|
steps := math.ceil(extent / PATCH_STEP_PIXELS)
|
|
if steps >= f64(MAX_PATCH_SUBDIV) {
|
|
n = MAX_PATCH_SUBDIV
|
|
} else if steps > 1 {
|
|
n = int(steps)
|
|
}
|
|
}
|
|
inv_n := 1 / f64(n)
|
|
|
|
row_a, row_b: [MAX_PATCH_SUBDIV + 1]Point
|
|
for j in 0 ..= n {
|
|
row_a[j] = patch_eval(&pole, 0, f64(j) * inv_n)
|
|
}
|
|
for i in 1 ..= n {
|
|
u1 := f64(i) * inv_n
|
|
u0 := f64(i - 1) * inv_n
|
|
for j in 0 ..= n {
|
|
row_b[j] = patch_eval(&pole, u1, f64(j) * inv_n)
|
|
}
|
|
for j in 0 ..< n {
|
|
v0 := f64(j) * inv_n
|
|
v1 := f64(j + 1) * inv_n
|
|
p00 := Shade_Vertex{row_a[j], patch_color(c, u0, v0)}
|
|
p01 := Shade_Vertex{row_a[j + 1], patch_color(c, u0, v1)}
|
|
p11 := Shade_Vertex{row_b[j + 1], patch_color(c, u1, v1)}
|
|
p10 := Shade_Vertex{row_b[j], patch_color(c, u1, v0)}
|
|
shade_triangle(ctx, p00, p01, p11)
|
|
shade_triangle(ctx, p00, p11, p10)
|
|
}
|
|
row_a = row_b
|
|
if ctx.fuel <= 0 || ctx.tris >= MAX_MESH_TRIANGLES {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// shade_coons_inner is the interior control point the format derives from the
|
|
// boundary of a Coons patch:
|
|
//
|
|
// p11 = (-4*p00 + 6*(p01 + p10) - 2*(p03 + p30) + 3*(p31 + p13) - p33) / 9
|
|
//
|
|
// with the arguments named for the corner it belongs to; the other three come
|
|
// from the same expression with the grid rotated.
|
|
@(private)
|
|
shade_coons_inner :: proc "contextless" (corner, along_a, along_b, far_a, far_b, near_a, near_b, opposite: Point) -> Point {
|
|
return Point{
|
|
(-4 * corner.x + 6 * (along_a.x + along_b.x) - 2 * (far_a.x + far_b.x) +
|
|
3 * (near_a.x + near_b.x) - opposite.x) / 9,
|
|
(-4 * corner.y + 6 * (along_a.y + along_b.y) - 2 * (far_a.y + far_b.y) +
|
|
3 * (near_a.y + near_b.y) - opposite.y) / 9,
|
|
}
|
|
}
|