initial commit
This commit is contained in:
+738
@@ -0,0 +1,738 @@
|
||||
package pdf
|
||||
|
||||
import "core:math"
|
||||
|
||||
// Blend modes and compositing (ISO 32000-2, 11.3.5 and 11.3.6).
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
// Which representation each procedure expects
|
||||
//
|
||||
// Mixing premultiplied and straight alpha is the classic source of dark
|
||||
// fringes, so the rule here is stated once and never bent:
|
||||
//
|
||||
// * Every `Bitmap` this file reads or writes -- `dst` and `src` in
|
||||
// `blend_span`, `blend_span_rgba8` and `blend_bitmap` -- is PREMULTIPLIED.
|
||||
// That is the internal representation the whole renderer composites in.
|
||||
// `bitmap_unpremultiply` is called once, at the very end, on the bitmap
|
||||
// that leaves the library.
|
||||
// * Every colour passed as an `f64` -- `rgba` in `blend_span`, the arguments
|
||||
// to `blend_separable` and `blend_non_separable`, `backdrop` in
|
||||
// `mask_from_bitmap` -- is STRAIGHT, in 0..1, with alpha separate. That is
|
||||
// what a `Paint` carries (`paint.rgb`, `paint.alpha`) and what the spec's
|
||||
// blend formulas are written in terms of.
|
||||
// * `blend_span_rgba8`'s `src` bytes are STRAIGHT RGBA8, because that is what
|
||||
// `image_decode` produces.
|
||||
// * Coverage and mask bytes are plain 0..255 fractions; they multiply into
|
||||
// the source alpha and are never premultiplied by anything.
|
||||
//
|
||||
// A freshly zeroed bitmap is simultaneously valid as straight and as
|
||||
// premultiplied data, which is why the raster device can start from
|
||||
// `bitmap_make` without a conversion. Compositing into a bitmap sets its
|
||||
// `premultiplied` flag, so a device that forgets to set it cannot end up
|
||||
// handing straight-labelled premultiplied pixels to the caller. A bitmap that
|
||||
// already holds STRAIGHT content must be run through `bitmap_premultiply`
|
||||
// before anything here touches it.
|
||||
//
|
||||
// The inner loops are f64 throughout. There is deliberately no separate
|
||||
// integer fast path for Normal: two paths that round differently would
|
||||
// disagree by a least-significant bit depending on which one a given span took,
|
||||
// and a seam down the middle of a fill is far worse than the cycles saved.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The separable blend functions (11.3.5.2)
|
||||
//
|
||||
// All arguments and results are in 0..1. `cb` is the backdrop colour, `cs` the
|
||||
// source colour, one component at a time.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// blend_separable evaluates B(cb, cs) for one component.
|
||||
//
|
||||
// The four non-separable modes cannot be evaluated one component at a time;
|
||||
// passing one here returns `cs`, i.e. Normal. Callers dispatch on
|
||||
// `blend_mode_is_separable` rather than relying on that, but a mode that
|
||||
// arrives here by accident must degrade to the identity rather than to noise.
|
||||
@(private)
|
||||
blend_separable :: proc(mode: Blend_Mode, cb, cs: f64) -> f64 {
|
||||
switch mode {
|
||||
case .Normal:
|
||||
return cs
|
||||
case .Multiply:
|
||||
return cb * cs
|
||||
case .Screen:
|
||||
return cb + cs - cb * cs
|
||||
case .Overlay:
|
||||
// Defined as HardLight with the operands exchanged.
|
||||
return blend_hard_light(cs, cb)
|
||||
case .Darken:
|
||||
return min(cb, cs)
|
||||
case .Lighten:
|
||||
return max(cb, cs)
|
||||
case .Color_Dodge:
|
||||
// ISO 32000-2 rewrote this case split; ISO 32000-1 said only
|
||||
// "cs < 1 ? min(1, cb/(1-cs)) : 1", which lights a black backdrop up
|
||||
// to white when the source is white. Testing cb == 0 first is what
|
||||
// stops that, and it is what every current implementation does.
|
||||
if cb <= 0 { return 0 }
|
||||
if cs >= 1 { return 1 }
|
||||
return min(1, cb / (1 - cs))
|
||||
case .Color_Burn:
|
||||
// Mirror image of ColorDodge: cb == 1 is tested before cs == 0, so a
|
||||
// white backdrop stays white instead of being burned to black.
|
||||
if cb >= 1 { return 1 }
|
||||
if cs <= 0 { return 0 }
|
||||
return 1 - min(1, (1 - cb) / cs)
|
||||
case .Hard_Light:
|
||||
return blend_hard_light(cb, cs)
|
||||
case .Soft_Light:
|
||||
return blend_soft_light(cb, cs)
|
||||
case .Difference:
|
||||
return abs(cb - cs)
|
||||
case .Exclusion:
|
||||
return cb + cs - 2 * cb * cs
|
||||
case .Hue, .Saturation, .Color, .Luminosity:
|
||||
return cs
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
@(private)
|
||||
blend_hard_light :: proc(cb, cs: f64) -> f64 {
|
||||
if cs <= 0.5 {
|
||||
return cb * (2 * cs) // Multiply(cb, 2*cs)
|
||||
}
|
||||
d := 2 * cs - 1
|
||||
return cb + d - cb * d // Screen(cb, 2*cs - 1)
|
||||
}
|
||||
|
||||
// blend_soft_light is the mode implementations most often get wrong: D(x) is
|
||||
// piecewise, and the low branch is a cubic, not sqrt(x). The two agree at
|
||||
// x = 0.25 (both give 0.5); they are 0.05 apart at x = 0.05, which is dark
|
||||
// shadow detail and plainly visible.
|
||||
@(private)
|
||||
blend_soft_light :: proc(cb, cs: f64) -> f64 {
|
||||
if cs <= 0.5 {
|
||||
return cb - (1 - 2 * cs) * cb * (1 - cb)
|
||||
}
|
||||
return cb + (2 * cs - 1) * (blend_soft_light_d(cb) - cb)
|
||||
}
|
||||
|
||||
@(private)
|
||||
blend_soft_light_d :: proc(x: f64) -> f64 {
|
||||
if x <= 0.25 {
|
||||
return ((16 * x - 12) * x + 4) * x
|
||||
}
|
||||
if x <= 0 { return 0 }
|
||||
return math.sqrt(x)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The non-separable blend functions (11.3.5.3)
|
||||
//
|
||||
// These act on all three components at once, through the spec's Lum, ClipColor,
|
||||
// SetLum, Sat and SetSat helpers. They are defined for RGB; a greyscale
|
||||
// destination is handled by replicating its single component into all three,
|
||||
// which is exactly what the formulas reduce to anyway.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(private)
|
||||
blend_lum :: proc(c: [3]f64) -> f64 {
|
||||
return 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2]
|
||||
}
|
||||
|
||||
// blend_clip_color pulls a colour back inside the unit cube without changing
|
||||
// its luminosity, by scaling it towards the grey axis.
|
||||
//
|
||||
// L, n and x are all taken from the ORIGINAL colour: the second test uses the
|
||||
// pre-clip maximum, not the maximum after the first branch has run. Recomputing
|
||||
// them between the branches is a real and easy mistake, and it changes the
|
||||
// result whenever both branches fire.
|
||||
@(private)
|
||||
blend_clip_color :: proc(c: [3]f64) -> [3]f64 {
|
||||
r := c
|
||||
l := blend_lum(c)
|
||||
n := min(c[0], c[1], c[2])
|
||||
x := max(c[0], c[1], c[2])
|
||||
if n < 0 {
|
||||
d := l - n
|
||||
// d == 0 needs all three components equal to l, and then n < 0 means
|
||||
// the colour is a uniform negative grey. Nothing can be scaled towards
|
||||
// the axis it is already on, so clamp instead of dividing by zero.
|
||||
if d <= 0 { return [3]f64{0, 0, 0} }
|
||||
for i in 0 ..< 3 { r[i] = l + ((r[i] - l) * l) / d }
|
||||
}
|
||||
if x > 1 {
|
||||
d := x - l
|
||||
if d <= 0 { return [3]f64{1, 1, 1} }
|
||||
for i in 0 ..< 3 { r[i] = l + ((r[i] - l) * (1 - l)) / d }
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@(private)
|
||||
blend_set_lum :: proc(c: [3]f64, l: f64) -> [3]f64 {
|
||||
d := l - blend_lum(c)
|
||||
return blend_clip_color([3]f64{c[0] + d, c[1] + d, c[2] + d})
|
||||
}
|
||||
|
||||
@(private)
|
||||
blend_sat :: proc(c: [3]f64) -> f64 {
|
||||
return max(c[0], c[1], c[2]) - min(c[0], c[1], c[2])
|
||||
}
|
||||
|
||||
// blend_set_sat rescales a colour to the given saturation, keeping the relative
|
||||
// ordering of its components and pinning the smallest to zero. The luminosity
|
||||
// it destroys is put back by the SetLum that always follows it.
|
||||
@(private)
|
||||
blend_set_sat :: proc(c: [3]f64, s: f64) -> [3]f64 {
|
||||
// Sort the three component indices, so the result can be written back to
|
||||
// the component it came from.
|
||||
lo, mid, hi := 0, 1, 2
|
||||
if c[lo] > c[mid] { lo, mid = mid, lo }
|
||||
if c[mid] > c[hi] { mid, hi = hi, mid }
|
||||
if c[lo] > c[mid] { lo, mid = mid, lo }
|
||||
|
||||
r: [3]f64
|
||||
if c[hi] > c[lo] {
|
||||
r[mid] = ((c[mid] - c[lo]) * s) / (c[hi] - c[lo])
|
||||
r[hi] = s
|
||||
} else {
|
||||
r[mid] = 0
|
||||
r[hi] = 0
|
||||
}
|
||||
r[lo] = 0
|
||||
return r
|
||||
}
|
||||
|
||||
// blend_non_separable evaluates B(Cb, Cs) for Hue, Saturation, Color and
|
||||
// Luminosity. A separable mode passed here is applied component by component,
|
||||
// so this is a complete B() for any mode.
|
||||
@(private)
|
||||
blend_non_separable :: proc(mode: Blend_Mode, cb, cs: [3]f64) -> [3]f64 {
|
||||
#partial switch mode {
|
||||
case .Hue:
|
||||
return blend_set_lum(blend_set_sat(cs, blend_sat(cb)), blend_lum(cb))
|
||||
case .Saturation:
|
||||
return blend_set_lum(blend_set_sat(cb, blend_sat(cs)), blend_lum(cb))
|
||||
case .Color:
|
||||
return blend_set_lum(cs, blend_lum(cb))
|
||||
case .Luminosity:
|
||||
return blend_set_lum(cb, blend_lum(cs))
|
||||
}
|
||||
return [3]f64{
|
||||
blend_separable(mode, cb[0], cs[0]),
|
||||
blend_separable(mode, cb[1], cs[1]),
|
||||
blend_separable(mode, cb[2], cs[2]),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The compositing formula (11.3.6)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// blend_over composites one source colour onto one backdrop colour.
|
||||
//
|
||||
// Both inputs are STRAIGHT: `cb`/`ab` is the backdrop, `cs`/`as_` the source,
|
||||
// all in 0..1. The result is PREMULTIPLIED colour plus its alpha, because that
|
||||
// is the form the destination bitmap stores and it is the form in which the
|
||||
// union formula
|
||||
//
|
||||
// co = as*(1-ab)*Cs + as*ab*B(Cb,Cs) + (1-as)*ab*Cb
|
||||
// ao = as + ab*(1-as)
|
||||
//
|
||||
// is actually written. This is the single reference implementation: every span
|
||||
// and bitmap loop below goes through it, so there is no second rounding path to
|
||||
// drift away from.
|
||||
@(private)
|
||||
blend_over :: proc(cb: [3]f64, ab: f64, cs: [3]f64, as_: f64, mode: Blend_Mode) -> (co: [3]f64, ao: f64) {
|
||||
b := cs
|
||||
// With no backdrop there is nothing to blend with: the B() term is
|
||||
// multiplied by ab, so skipping it is exact, not an approximation.
|
||||
if mode != .Normal && ab > 0 {
|
||||
if blend_mode_is_separable(mode) {
|
||||
b[0] = blend_separable(mode, cb[0], cs[0])
|
||||
b[1] = blend_separable(mode, cb[1], cs[1])
|
||||
b[2] = blend_separable(mode, cb[2], cs[2])
|
||||
} else {
|
||||
b = blend_non_separable(mode, cb, cs)
|
||||
}
|
||||
}
|
||||
for i in 0 ..< 3 {
|
||||
co[i] = as_ * ((1 - ab) * cs[i] + ab * b[i]) + (1 - as_) * ab * cb[i]
|
||||
}
|
||||
ao = as_ + ab * (1 - as_)
|
||||
return
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pixel access
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// blend_quantize turns a 0..1 value into a byte, rounding half up. NaN maps to
|
||||
// 0: `!(v > 0)` is true for NaN, which is the point of writing the test that
|
||||
// way round.
|
||||
@(private)
|
||||
blend_quantize :: proc(v: f64) -> u8 {
|
||||
if !(v > 0) { return 0 }
|
||||
if v >= 1 { return 255 }
|
||||
return u8(v * 255 + 0.5)
|
||||
}
|
||||
|
||||
// blend_clamp01 forces a colour or alpha coming from a content stream into
|
||||
// 0..1. NaN maps to 0 and the infinities to the end they point at, which is
|
||||
// what clamping means for a value that merely overshot; `!(v > 0)` is the test
|
||||
// that catches NaN, since every comparison with a NaN is false.
|
||||
@(private)
|
||||
blend_clamp01 :: proc(v: f64) -> f64 {
|
||||
if !(v > 0) { return 0 }
|
||||
if !(v < 1) { return 1 }
|
||||
return v
|
||||
}
|
||||
|
||||
// blend_has_alpha reports whether a component count carries an alpha channel:
|
||||
// 2 is grey+alpha and 4 is RGBA, while 1 and 3 are opaque.
|
||||
@(private)
|
||||
blend_has_alpha :: proc(n: int) -> bool {
|
||||
return n == 2 || n == 4
|
||||
}
|
||||
|
||||
// blend_read_straight unpacks one PREMULTIPLIED pixel into a straight RGB
|
||||
// triple and its alpha. A grey pixel is replicated across all three
|
||||
// components; a pixel with no alpha channel reads as fully opaque.
|
||||
@(private)
|
||||
blend_read_straight :: proc(row: []u8, off, n: int) -> (c: [3]f64, a: f64) {
|
||||
if blend_has_alpha(n) {
|
||||
a8 := row[off + n - 1]
|
||||
a = f64(a8) / 255
|
||||
if a8 == 0 {
|
||||
// The colour of a fully transparent pixel is not recoverable, and
|
||||
// it is multiplied by ab = 0 everywhere it is used.
|
||||
return [3]f64{0, 0, 0}, 0
|
||||
}
|
||||
inv := 1 / f64(a8)
|
||||
if n == 2 {
|
||||
g := f64(row[off]) * inv
|
||||
return [3]f64{g, g, g}, a
|
||||
}
|
||||
return [3]f64{f64(row[off]) * inv, f64(row[off + 1]) * inv, f64(row[off + 2]) * inv}, a
|
||||
}
|
||||
if n == 1 {
|
||||
g := f64(row[off]) / 255
|
||||
return [3]f64{g, g, g}, 1
|
||||
}
|
||||
return [3]f64{f64(row[off]) / 255, f64(row[off + 1]) / 255, f64(row[off + 2]) / 255}, 1
|
||||
}
|
||||
|
||||
// blend_write_premul stores a PREMULTIPLIED result. For a greyscale
|
||||
// destination the three components are equal by construction -- the source was
|
||||
// reduced to grey before blending -- so component 0 is the whole answer.
|
||||
@(private)
|
||||
blend_write_premul :: proc(row: []u8, off, n: int, co: [3]f64, ao: f64) {
|
||||
if n <= 2 {
|
||||
row[off] = blend_quantize(co[0])
|
||||
} else {
|
||||
row[off] = blend_quantize(co[0])
|
||||
row[off + 1] = blend_quantize(co[1])
|
||||
row[off + 2] = blend_quantize(co[2])
|
||||
}
|
||||
if blend_has_alpha(n) {
|
||||
row[off + n - 1] = blend_quantize(ao)
|
||||
}
|
||||
}
|
||||
|
||||
// blend_source_for reduces a straight RGB source to the destination's colour
|
||||
// model. A greyscale destination gets the luminosity, which is the same
|
||||
// weighting the non-separable modes use, so grey output stays consistent with
|
||||
// what those modes would have produced.
|
||||
@(private)
|
||||
blend_source_for :: proc(n: int, rgb: [3]f64) -> [3]f64 {
|
||||
if n <= 2 {
|
||||
g := blend_lum(rgb)
|
||||
return [3]f64{g, g, g}
|
||||
}
|
||||
return rgb
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Spans
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// blend_span composites one horizontal run of coverage in a single colour onto
|
||||
// `dst`, which must be PREMULTIPLIED.
|
||||
//
|
||||
// y, x0 the first pixel of the run, in `dst` pixel coordinates
|
||||
// coverage anti-aliasing coverage, 0..255, one byte per pixel from x0
|
||||
// rgba the source colour: STRAIGHT RGB in 0..1 plus its alpha, which is
|
||||
// the constant alpha of the paint operation
|
||||
// mask an optional soft-mask row, 0..255, aligned with `coverage` so
|
||||
// that mask[i] modulates the same pixel coverage[i] does. nil means
|
||||
// no mask. Bytes past the end of a short mask read as 255 rather
|
||||
// than erasing content that has no mask data.
|
||||
//
|
||||
// The effective source alpha of a pixel is rgba.a * coverage/255 * mask/255.
|
||||
// Runs that fall partly or wholly outside `dst` are clipped, not rejected: the
|
||||
// rasteriser is allowed to hand over spans that start left of zero.
|
||||
@(private)
|
||||
blend_span :: proc(dst: ^Bitmap, y, x0: int, coverage: []u8, rgba: [4]f64,
|
||||
mode: Blend_Mode, mask: []u8 = nil) {
|
||||
if dst == nil || len(coverage) == 0 { return }
|
||||
|
||||
sa := blend_clamp01(rgba[3])
|
||||
if sa <= 0 { return }
|
||||
|
||||
row := bitmap_row(dst, y)
|
||||
if row == nil { return }
|
||||
|
||||
// Clip the run to the bitmap. `skip` is how many coverage bytes fall off
|
||||
// the left edge, so the mask stays in step with the coverage.
|
||||
skip := 0
|
||||
x := x0
|
||||
if x < 0 {
|
||||
skip = -x
|
||||
x = 0
|
||||
}
|
||||
if skip >= len(coverage) { return }
|
||||
count := min(len(coverage) - skip, dst.width - x)
|
||||
if count <= 0 { return }
|
||||
|
||||
n := dst.n
|
||||
cs := blend_source_for(n, [3]f64{
|
||||
blend_clamp01(rgba[0]), blend_clamp01(rgba[1]), blend_clamp01(rgba[2]),
|
||||
})
|
||||
|
||||
for i in 0 ..< count {
|
||||
cov := coverage[skip + i]
|
||||
if cov == 0 { continue }
|
||||
m := u8(255)
|
||||
if mask != nil {
|
||||
mi := skip + i
|
||||
m = mi < len(mask) ? mask[mi] : 255
|
||||
}
|
||||
if m == 0 { continue }
|
||||
|
||||
as_ := sa * (f64(cov) / 255) * (f64(m) / 255)
|
||||
if as_ <= 0 { continue }
|
||||
|
||||
off := (x + i) * n
|
||||
if off + n > len(row) { break }
|
||||
cb, ab := blend_read_straight(row, off, n)
|
||||
co, ao := blend_over(cb, ab, cs, as_, mode)
|
||||
blend_write_premul(row, off, n, co, ao)
|
||||
}
|
||||
|
||||
if blend_has_alpha(n) { dst.premultiplied = true }
|
||||
}
|
||||
|
||||
// blend_span_rgba8 is blend_span for a run whose colour varies per pixel: what
|
||||
// an image or a shading paints.
|
||||
//
|
||||
// `src` is STRAIGHT RGBA8, four bytes per pixel, starting at destination pixel
|
||||
// x0; that is the form `image_decode` hands back. `alpha` is the constant alpha
|
||||
// of the paint operation, multiplied into every pixel's own alpha. `coverage`
|
||||
// may be nil, meaning fully covered; when present it must be one byte per
|
||||
// source pixel. The run length is len(src)/4.
|
||||
@(private)
|
||||
blend_span_rgba8 :: proc(dst: ^Bitmap, y, x0: int, src: []u8, alpha: f64,
|
||||
mode: Blend_Mode, coverage: []u8 = nil, mask: []u8 = nil) {
|
||||
if dst == nil || len(src) < 4 { return }
|
||||
|
||||
ga := blend_clamp01(alpha)
|
||||
if ga <= 0 { return }
|
||||
|
||||
row := bitmap_row(dst, y)
|
||||
if row == nil { return }
|
||||
|
||||
skip := 0
|
||||
x := x0
|
||||
if x < 0 {
|
||||
skip = -x
|
||||
x = 0
|
||||
}
|
||||
total := len(src) / 4
|
||||
if skip >= total { return }
|
||||
count := min(total - skip, dst.width - x)
|
||||
if count <= 0 { return }
|
||||
|
||||
n := dst.n
|
||||
for i in 0 ..< count {
|
||||
si := (skip + i) * 4
|
||||
sa8 := src[si + 3]
|
||||
if sa8 == 0 { continue }
|
||||
|
||||
cov := u8(255)
|
||||
if coverage != nil {
|
||||
ci := skip + i
|
||||
cov = ci < len(coverage) ? coverage[ci] : 255
|
||||
}
|
||||
if cov == 0 { continue }
|
||||
|
||||
m := u8(255)
|
||||
if mask != nil {
|
||||
mi := skip + i
|
||||
m = mi < len(mask) ? mask[mi] : 255
|
||||
}
|
||||
if m == 0 { continue }
|
||||
|
||||
as_ := ga * (f64(sa8) / 255) * (f64(cov) / 255) * (f64(m) / 255)
|
||||
if as_ <= 0 { continue }
|
||||
|
||||
cs := blend_source_for(n, [3]f64{
|
||||
f64(src[si]) / 255, f64(src[si + 1]) / 255, f64(src[si + 2]) / 255,
|
||||
})
|
||||
|
||||
off := (x + i) * n
|
||||
if off + n > len(row) { break }
|
||||
cb, ab := blend_read_straight(row, off, n)
|
||||
co, ao := blend_over(cb, ab, cs, as_, mode)
|
||||
blend_write_premul(row, off, n, co, ao)
|
||||
}
|
||||
|
||||
if blend_has_alpha(n) { dst.premultiplied = true }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Whole-bitmap compositing: transparency groups
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// blend_bitmap_restore puts `src` BACK onto `dst`, replacing rather than
|
||||
// compositing, and interpolating towards what `dst` already holds wherever the
|
||||
// mask is not opaque.
|
||||
//
|
||||
// This is what finishes a NON-ISOLATED transparency group (ISO 32000-1,
|
||||
// 11.4.6). Such a group is handed a copy of its backdrop as its initial
|
||||
// contents, so its finished bitmap already IS "contents over backdrop" -- and
|
||||
// compositing that over the backdrop a second time blends the backdrop with
|
||||
// itself. `C over C` under Normal gives ao = a + a(1-a) = 2a - a^2, so a
|
||||
// backdrop pixel at alpha 0.5 comes back at 0.75, which is wrong everywhere the
|
||||
// backdrop is not already opaque: any group nested inside another group, and
|
||||
// every page rendered with a transparent background. Replacing is exact for
|
||||
// both, and reduces to the old composite wherever the backdrop was opaque.
|
||||
//
|
||||
// `mask` is optional and is aligned with `src`, exactly as `blend_bitmap`'s is.
|
||||
// Both bitmaps are PREMULTIPLIED.
|
||||
@(private)
|
||||
blend_bitmap_restore :: proc(dst, src: ^Bitmap, x, y: int, mask: ^Bitmap) {
|
||||
if dst == nil || src == nil { return }
|
||||
|
||||
dn := dst.n
|
||||
sn := src.n
|
||||
|
||||
sx0 := max(0, -x)
|
||||
sy0 := max(0, -y)
|
||||
sx1 := min(src.width, dst.width - x)
|
||||
sy1 := min(src.height, dst.height - y)
|
||||
if sx1 <= sx0 || sy1 <= sy0 { return }
|
||||
|
||||
for sy in sy0 ..< sy1 {
|
||||
srow := bitmap_row(src, sy)
|
||||
drow := bitmap_row(dst, y + sy)
|
||||
if srow == nil || drow == nil { continue }
|
||||
mrow: []u8
|
||||
if mask != nil { mrow = bitmap_row(mask, sy) }
|
||||
|
||||
for sx in sx0 ..< sx1 {
|
||||
soff := sx * sn
|
||||
if soff + sn > len(srow) { break }
|
||||
doff := (x + sx) * dn
|
||||
if doff + dn > len(drow) { break }
|
||||
|
||||
m := u8(255)
|
||||
if mask != nil {
|
||||
moff := sx * mask.n
|
||||
if mrow != nil && moff < len(mrow) { m = mrow[moff] } else { m = 255 }
|
||||
}
|
||||
if m == 0 { continue }
|
||||
|
||||
cs_raw, sa := blend_read_straight(srow, soff, sn)
|
||||
cs := blend_source_for(dn, cs_raw)
|
||||
if m == 255 {
|
||||
blend_write_premul(drow, doff, dn, [3]f64{cs[0] * sa, cs[1] * sa, cs[2] * sa}, sa)
|
||||
continue
|
||||
}
|
||||
// Interpolate in PREMULTIPLIED space: a transparent pixel has no
|
||||
// recoverable colour, and lerping straight colour towards it would
|
||||
// drag a black fringe in.
|
||||
t := f64(m) / 255
|
||||
cb, ab := blend_read_straight(drow, doff, dn)
|
||||
ao := ab + (sa - ab) * t
|
||||
co: [3]f64
|
||||
for i in 0 ..< 3 {
|
||||
co[i] = cb[i] * ab + (cs[i] * sa - cb[i] * ab) * t
|
||||
}
|
||||
blend_write_premul(drow, doff, dn, co, ao)
|
||||
}
|
||||
}
|
||||
|
||||
if blend_has_alpha(dn) { dst.premultiplied = true }
|
||||
}
|
||||
|
||||
// blend_bitmap composites `src` onto `dst` with its top-left corner at
|
||||
// (x, y) in `dst`. This is what `end_group` does with a rendered transparency
|
||||
// group (11.4.6): the group was rendered into its own bitmap, and its constant
|
||||
// alpha, blend mode and soft mask are applied once, here, to the group as a
|
||||
// whole rather than to each operation inside it.
|
||||
//
|
||||
// Both bitmaps are PREMULTIPLIED. `alpha` is the group's constant alpha.
|
||||
//
|
||||
// `mask` is optional and is aligned with `src`, not with `dst`: mask pixel
|
||||
// (i, j) modulates src pixel (i, j). A device holding a device-space soft mask
|
||||
// passes a borrowed window onto it, which `Bitmap`'s independent `stride`
|
||||
// exists to allow. Only component 0 of the mask is read, which is what
|
||||
// `mask_from_bitmap` produces for both mask types. Pixels outside the mask read
|
||||
// as 255, so a mask that is accidentally too small shows content rather than
|
||||
// erasing it.
|
||||
@(private)
|
||||
blend_bitmap :: proc(dst, src: ^Bitmap, x, y: int, alpha: f64, mode: Blend_Mode, mask: ^Bitmap) {
|
||||
if dst == nil || src == nil { return }
|
||||
|
||||
ga := blend_clamp01(alpha)
|
||||
if ga <= 0 { return }
|
||||
|
||||
dn := dst.n
|
||||
sn := src.n
|
||||
|
||||
// Clip the placement to the destination once, rather than testing every
|
||||
// pixel: a group whose bitmap hangs off the page is the normal case.
|
||||
sx0 := max(0, -x)
|
||||
sy0 := max(0, -y)
|
||||
sx1 := min(src.width, dst.width - x)
|
||||
sy1 := min(src.height, dst.height - y)
|
||||
if sx1 <= sx0 || sy1 <= sy0 { return }
|
||||
|
||||
for sy in sy0 ..< sy1 {
|
||||
srow := bitmap_row(src, sy)
|
||||
drow := bitmap_row(dst, y + sy)
|
||||
if srow == nil || drow == nil { continue }
|
||||
mrow: []u8
|
||||
if mask != nil { mrow = bitmap_row(mask, sy) }
|
||||
|
||||
for sx in sx0 ..< sx1 {
|
||||
soff := sx * sn
|
||||
if soff + sn > len(srow) { break }
|
||||
|
||||
cs_raw, sa := blend_read_straight(srow, soff, sn)
|
||||
if sa <= 0 { continue }
|
||||
|
||||
m := u8(255)
|
||||
if mask != nil {
|
||||
moff := sx * mask.n
|
||||
if mrow != nil && moff < len(mrow) { m = mrow[moff] } else { m = 255 }
|
||||
}
|
||||
if m == 0 { continue }
|
||||
|
||||
as_ := sa * ga * (f64(m) / 255)
|
||||
if as_ <= 0 { continue }
|
||||
|
||||
doff := (x + sx) * dn
|
||||
if doff + dn > len(drow) { break }
|
||||
cs := blend_source_for(dn, cs_raw)
|
||||
cb, ab := blend_read_straight(drow, doff, dn)
|
||||
co, ao := blend_over(cb, ab, cs, as_, mode)
|
||||
blend_write_premul(drow, doff, dn, co, ao)
|
||||
}
|
||||
}
|
||||
|
||||
if blend_has_alpha(dn) { dst.premultiplied = true }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Soft masks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// mask_from_bitmap turns a rendered group bitmap into a one-component mask
|
||||
// (ISO 32000-2, 11.6.5).
|
||||
//
|
||||
// luminosity false selects an alpha mask -- the mask value is the group's
|
||||
// alpha and its colour is ignored. true selects a luminosity
|
||||
// mask: the group is composited onto an opaque backdrop and the
|
||||
// mask value is the luminosity of the result.
|
||||
// backdrop the /BC colour, already converted to RGB, used only by a
|
||||
// luminosity mask. Black is the default the format specifies,
|
||||
// and it is what makes the area a group does not cover mask
|
||||
// everything out.
|
||||
// transfer the /TR function, or nil. It is evaluated once per possible
|
||||
// byte value into a 256-entry table rather than once per pixel:
|
||||
// a type 4 PostScript transfer over a full page would otherwise
|
||||
// run the calculator several million times.
|
||||
//
|
||||
// `src` may be premultiplied or straight; its `premultiplied` flag decides.
|
||||
// The result is a fresh `n = 1` Bitmap the same size as `src`, allocated with
|
||||
// `allocator` and OWNED BY THE CALLER -- free it with `bitmap_destroy`.
|
||||
@(private)
|
||||
mask_from_bitmap :: proc(src: ^Bitmap, luminosity: bool, backdrop: [3]f64,
|
||||
transfer: ^Function, allocator := context.allocator) -> (out: Bitmap, err: Error) {
|
||||
if src == nil || src.width <= 0 || src.height <= 0 { return {}, .Invalid_Argument }
|
||||
|
||||
out = bitmap_make(src.width, src.height, 1, allocator) or_return
|
||||
|
||||
lut := blend_transfer_table(transfer)
|
||||
|
||||
bd := [3]f64{blend_clamp01(backdrop[0]), blend_clamp01(backdrop[1]), blend_clamp01(backdrop[2])}
|
||||
bd_lum_v := blend_quantize(blend_lum(bd))
|
||||
|
||||
sn := src.n
|
||||
has_a := blend_has_alpha(sn)
|
||||
|
||||
for j in 0 ..< src.height {
|
||||
srow := bitmap_row(src, j)
|
||||
drow := bitmap_row(&out, j)
|
||||
if srow == nil || drow == nil { continue }
|
||||
|
||||
for i in 0 ..< src.width {
|
||||
soff := i * sn
|
||||
if soff + sn > len(srow) { break }
|
||||
|
||||
v: u8
|
||||
if !luminosity {
|
||||
v = has_a ? srow[soff + sn - 1] : 255
|
||||
} else {
|
||||
a := has_a ? f64(srow[soff + sn - 1]) / 255 : 1.0
|
||||
if a <= 0 {
|
||||
// Nothing was painted here, so the mask is the backdrop.
|
||||
v = bd_lum_v
|
||||
} else {
|
||||
c: [3]f64
|
||||
if sn <= 2 {
|
||||
g := f64(srow[soff]) / 255
|
||||
c = {g, g, g}
|
||||
} else {
|
||||
c = {f64(srow[soff]) / 255, f64(srow[soff + 1]) / 255, f64(srow[soff + 2]) / 255}
|
||||
}
|
||||
// Composite over the opaque backdrop. A premultiplied
|
||||
// source already carries the a*C factor; a straight one
|
||||
// does not.
|
||||
if !src.premultiplied {
|
||||
c = {c[0] * a, c[1] * a, c[2] * a}
|
||||
}
|
||||
inv := 1 - a
|
||||
v = blend_quantize(blend_lum([3]f64{
|
||||
c[0] + inv * bd[0], c[1] + inv * bd[1], c[2] + inv * bd[2],
|
||||
}))
|
||||
}
|
||||
}
|
||||
drow[i] = lut[v]
|
||||
}
|
||||
}
|
||||
return out, .None
|
||||
}
|
||||
|
||||
// blend_transfer_table precomputes fn over the 256 byte values. An absent,
|
||||
// malformed or wrongly-shaped function yields the identity, because a soft
|
||||
// mask with an unusable /TR must still mask (11.6.5.2) rather than vanish.
|
||||
@(private)
|
||||
blend_transfer_table :: proc(fn: ^Function) -> (lut: [256]u8) {
|
||||
for i in 0 ..< 256 { lut[i] = u8(i) }
|
||||
if fn == nil || fn.n_in != 1 || fn.n_out < 1 { return }
|
||||
|
||||
in_buf: [1]f64
|
||||
// Sized for the widest function the library will build, so a function that
|
||||
// reports one output but writes n_out cannot run off the end.
|
||||
out_buf: [MAX_FUNCTION_OUTPUTS]f64
|
||||
for i in 0 ..< 256 {
|
||||
in_buf[0] = f64(i) / 255
|
||||
out_buf = {}
|
||||
function_eval(fn, in_buf[:], out_buf[:])
|
||||
lut[i] = blend_quantize(out_buf[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user