initial commit
This commit is contained in:
@@ -0,0 +1,814 @@
|
||||
package pdf
|
||||
|
||||
import "core:testing"
|
||||
|
||||
// The fixtures -- `cf_open`, `cf_parse`, `cf_stream`, `cf_bytes` -- live in
|
||||
// function_test.odin, because a colour space is not testable without the
|
||||
// functions its tint transforms are made of.
|
||||
|
||||
@(private = "file")
|
||||
TOL :: 1e-9
|
||||
|
||||
@(private = "file")
|
||||
expect_rgb :: proc(t: ^testing.T, got: [3]f64, r, g, b: f64, tol: f64, what: string,
|
||||
loc := #caller_location) {
|
||||
if !(abs(got[0] - r) <= tol && abs(got[1] - g) <= tol && abs(got[2] - b) <= tol) {
|
||||
testing.expectf(t, false, "%s: got (%f %f %f), want (%f %f %f)",
|
||||
what, got[0], got[1], got[2], r, g, b, loc = loc)
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
load_cs :: proc(t: ^testing.T, doc: ^Document, src: string, res: ^Dict = nil) -> ^Colorspace {
|
||||
cs, err := colorspace_load(doc, cf_parse(doc, src), res)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
return cs
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
cf_array :: proc(doc: ^Document, items: ..Object) -> ^Array {
|
||||
a := new_array(doc.allocator, max(len(items), 1))
|
||||
for it in items {
|
||||
array_push(a, it)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The device spaces
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_device_gray :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := colorspace_device_gray(doc)
|
||||
testing.expect_value(t, cs.kind, Colorspace_Kind.Device_Gray)
|
||||
testing.expect_value(t, cs.n, 1)
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 0, 0, 0, TOL, "black")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 1, 1, TOL, "white")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.25)), 0.25, 0.25, 0.25, TOL, "quarter")
|
||||
|
||||
// Components outside 0..1 are clamped, not wrapped.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(2)), 1, 1, 1, TOL, "too bright")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(-1)), 0, 0, 0, TOL, "negative")
|
||||
|
||||
// Naming it produces the very same space, not a copy.
|
||||
named := load_cs(t, doc, "/DeviceGray")
|
||||
testing.expect(t, named == cs, "/DeviceGray must be the shared space")
|
||||
testing.expect(t, load_cs(t, doc, "[/DeviceGray]") == cs, "and so must [/DeviceGray]")
|
||||
testing.expect(t, load_cs(t, doc, "/G") == cs, "and the inline-image abbreviation")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_device_rgb :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := colorspace_device_rgb(doc)
|
||||
testing.expect_value(t, cs.n, 3)
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 0, 0)), 0, 0, 0, TOL, "black")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1, 1, 1)), 1, 1, 1, TOL, "white")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1, 0.5, 0)), 1, 0.5, 0, TOL, "orange")
|
||||
// A colour with too few components reads the missing ones as zero.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 0, 0, TOL, "short colour")
|
||||
|
||||
testing.expect(t, load_cs(t, doc, "/DeviceRGB") == cs, "/DeviceRGB")
|
||||
testing.expect(t, load_cs(t, doc, "/RGB") == cs, "/RGB abbreviation")
|
||||
}
|
||||
|
||||
// The naive conversion: subtract each ink and the black from full intensity.
|
||||
// It is what every non-colour-managed viewer does.
|
||||
@(test)
|
||||
test_colorspace_device_cmyk :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := colorspace_device_cmyk(doc)
|
||||
testing.expect_value(t, cs.n, 4)
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 0, 0, 0)), 1, 1, 1, TOL, "no ink is white")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 0, 0, 1)), 0, 0, 0, TOL, "black ink")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1, 0, 0, 0)), 0, 1, 1, TOL, "cyan")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 1, 0, 0)), 1, 0, 1, TOL, "magenta")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 0, 1, 0)), 1, 1, 0, TOL, "yellow")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 1, 1, 0)), 1, 0, 0, TOL, "red")
|
||||
// Half a cyan under a quarter black: 1 - (0.5 + 0.25) against 1 - 0.25.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.5, 0, 0, 0.25)),
|
||||
0.25, 0.75, 0.75, TOL, "ink plus black")
|
||||
// Ink and black together past 1 saturate rather than going negative.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1, 1, 1, 1)), 0, 0, 0, TOL, "everything")
|
||||
|
||||
testing.expect(t, load_cs(t, doc, "/DeviceCMYK") == cs, "/DeviceCMYK")
|
||||
testing.expect(t, load_cs(t, doc, "/CMYK") == cs, "/CMYK abbreviation")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CalGray and CalRGB
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// With the default /Gamma these behave exactly as the device spaces, which is
|
||||
// what the format permits and what viewers do.
|
||||
@(test)
|
||||
test_colorspace_cal_defaults_to_device :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
gray := load_cs(t, doc, "[/CalGray << /WhitePoint [0.9505 1 1.089] >>]")
|
||||
if gray == nil { return }
|
||||
testing.expect_value(t, gray.kind, Colorspace_Kind.Cal_Gray)
|
||||
testing.expect_value(t, gray.n, 1)
|
||||
expect_rgb(t, colorspace_to_rgb(gray, color_make(0.25)), 0.25, 0.25, 0.25, TOL, "gamma 1 gray")
|
||||
|
||||
rgb := load_cs(t, doc, "[/CalRGB << /WhitePoint [0.9505 1 1.089] >>]")
|
||||
if rgb == nil { return }
|
||||
testing.expect_value(t, rgb.kind, Colorspace_Kind.Cal_RGB)
|
||||
testing.expect_value(t, rgb.n, 3)
|
||||
expect_rgb(t, colorspace_to_rgb(rgb, color_make(1, 0.5, 0)), 1, 0.5, 0, TOL, "gamma 1 rgb")
|
||||
|
||||
// A white point that would divide by zero is refused; the space still
|
||||
// loads, with the format's default.
|
||||
bad := load_cs(t, doc, "[/CalGray << /WhitePoint [0 0 0] /Gamma 1 >>]")
|
||||
if bad != nil {
|
||||
testing.expect(t, bad.white_point[0] > 0, "a zero white point is replaced")
|
||||
}
|
||||
}
|
||||
|
||||
// /Gamma is honoured by decoding to linear light and re-encoding for sRGB. At
|
||||
// the usual 2.2 that round trip lands within half a level of 255 of leaving
|
||||
// the value alone, so the common case still agrees with every other viewer.
|
||||
@(test)
|
||||
test_colorspace_calgray_gamma :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := load_cs(t, doc, "[/CalGray << /WhitePoint [0.9505 1 1.089] /Gamma 2.2 >>]")
|
||||
if cs == nil { return }
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 0, 0, 0, TOL, "black stays black")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 1, 1, TOL, "white stays white")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.5)),
|
||||
0.5038667822745281, 0.5038667822745281, 0.5038667822745281, 1e-9,
|
||||
"gamma 2.2 round trip at 0.5")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.25)),
|
||||
0.2410493652415971, 0.2410493652415971, 0.2410493652415971, 1e-9,
|
||||
"gamma 2.2 round trip at 0.25")
|
||||
// Which is to say: within one level of 255 of the DeviceGray answer.
|
||||
got := colorspace_to_rgb(cs, color_make(0.5))
|
||||
testing.expect(t, abs(got[0] - 0.5) < 1.0 / 255, "still what a viewer shows")
|
||||
|
||||
// A per-component /Gamma on CalRGB.
|
||||
rgb := load_cs(t, doc,
|
||||
"[/CalRGB << /WhitePoint [0.9505 1 1.089] /Gamma [2.2 1 2.2] >>]")
|
||||
if rgb == nil { return }
|
||||
out := colorspace_to_rgb(rgb, color_make(0.5, 0.5, 0.5))
|
||||
testing.expect(t, abs(out[0] - 0.5038667822745281) < 1e-9, "red honours its gamma")
|
||||
testing.expect(t, abs(out[1] - 0.5) < 1e-12, "green is left alone at gamma 1")
|
||||
testing.expect(t, abs(out[2] - 0.5038667822745281) < 1e-9, "blue honours its gamma")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lab
|
||||
//
|
||||
// The reference values are the published sRGB primaries expressed in L*a*b*
|
||||
// under a D50 white point, which is the white point PDF Lab spaces almost
|
||||
// always carry. Getting back to within a level of 255 of pure red, green and
|
||||
// blue is what says the conversion -- including the Bradford adaptation from
|
||||
// D50 to sRGB's D65 -- is right.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_lab_known_colours :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := load_cs(t, doc,
|
||||
"[/Lab << /WhitePoint [0.9642 1 0.8249] /Range [-128 127 -128 127] >>]")
|
||||
if cs == nil { return }
|
||||
testing.expect_value(t, cs.kind, Colorspace_Kind.Lab)
|
||||
testing.expect_value(t, cs.n, 3)
|
||||
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(100, 0, 0)), 1, 1, 1, 1e-6, "L* 100 is white")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 0, 0)), 0, 0, 0, 1e-9, "L* 0 is black")
|
||||
// L* 53.585 is the mid grey of sRGB: 128/255.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(53.5850, 0, 0)),
|
||||
128.0 / 255, 128.0 / 255, 128.0 / 255, 1e-5, "L* 53.585 is sRGB 128")
|
||||
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(54.2917, 80.8125, 69.8911)),
|
||||
1, 0, 0, 0.002, "sRGB red")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(87.8181, -79.2718, 80.9946)),
|
||||
0, 1, 0, 0.002, "sRGB green")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(29.5676, 68.2986, -112.0294)),
|
||||
0, 0, 1, 0.002, "sRGB blue")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_lab_d65_and_clamping :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
// The same conversion under a D65 white point, where sRGB red has
|
||||
// different coordinates.
|
||||
d65 := load_cs(t, doc, "[/Lab << /WhitePoint [0.9505 1 1.089] >>]")
|
||||
if d65 == nil { return }
|
||||
expect_rgb(t, colorspace_to_rgb(d65, color_make(53.2408, 80.0925, 67.2032)),
|
||||
1, 0, 0, 0.002, "sRGB red under D65")
|
||||
expect_rgb(t, colorspace_to_rgb(d65, color_make(100, 0, 0)), 1, 1, 1, 1e-6, "white under D65")
|
||||
|
||||
// /Range clamps a and b before anything else happens, so a wild value
|
||||
// cannot walk out of the gamut and take the whole colour with it.
|
||||
narrow := load_cs(t, doc,
|
||||
"[/Lab << /WhitePoint [0.9642 1 0.8249] /Range [-10 10 -10 10] >>]")
|
||||
if narrow == nil { return }
|
||||
testing.expect_value(t, narrow.range[0], -10)
|
||||
a := colorspace_to_rgb(narrow, color_make(50, 100, 0))
|
||||
b := colorspace_to_rgb(narrow, color_make(50, 10, 0))
|
||||
expect_rgb(t, a, b[0], b[1], b[2], 1e-12, "a is clamped to /Range")
|
||||
|
||||
// L* outside 0..100 is clamped too.
|
||||
hi := colorspace_to_rgb(narrow, color_make(500, 0, 0))
|
||||
expect_rgb(t, hi, 1, 1, 1, 1e-6, "L* above 100")
|
||||
lo := colorspace_to_rgb(narrow, color_make(-500, 0, 0))
|
||||
expect_rgb(t, lo, 0, 0, 0, 1e-9, "L* below 0")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ICCBased
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_iccbased :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
// A colour space is a name or an array, never a bare stream: ICCBased is
|
||||
// always written [/ICCBased stream] (ISO 32000-2, 8.6.5.5). Handing the
|
||||
// stream over on its own must be refused rather than guessed at, or a
|
||||
// stream that happens to carry an /N would be read as a colour space
|
||||
// wherever one is expected.
|
||||
_, bare_err := colorspace_load(doc, cf_stream(doc, "<< /N 3 >>", nil), nil)
|
||||
testing.expect_value(t, bare_err, Error.Bad_Colorspace)
|
||||
|
||||
// No /Alternate: the device space /N implies.
|
||||
icc3_arr := cf_array(doc, Name("ICCBased"), cf_stream(doc, "<< /N 3 >>", nil))
|
||||
icc3, icc3_err := colorspace_load(doc, icc3_arr, nil)
|
||||
testing.expect_value(t, icc3_err, Error.None)
|
||||
if icc3 == nil {
|
||||
testing.expect(t, false, "an ICCBased space with /N 3 must load")
|
||||
return
|
||||
}
|
||||
testing.expect_value(t, icc3.n, 3)
|
||||
expect_rgb(t, colorspace_to_rgb(icc3, color_make(1, 0, 0)), 1, 0, 0, TOL,
|
||||
"three components render as RGB")
|
||||
// The array form is what a file actually writes; the stream alone is what
|
||||
// cf_stream can build, so both are exercised.
|
||||
arr := cf_array(doc, Name("ICCBased"), cf_stream(doc, "<< /N 4 >>", nil))
|
||||
icc4, err4 := colorspace_load(doc, arr, nil)
|
||||
testing.expect_value(t, err4, Error.None)
|
||||
if icc4 == nil { return }
|
||||
testing.expect_value(t, icc4.kind, Colorspace_Kind.ICC_Based)
|
||||
testing.expect_value(t, icc4.n, 4)
|
||||
expect_rgb(t, colorspace_to_rgb(icc4, color_make(0, 1, 1, 0)), 1, 0, 0, TOL,
|
||||
"four components render as CMYK")
|
||||
|
||||
// An /Alternate is used when it agrees with /N.
|
||||
alt := cf_array(doc, Name("ICCBased"),
|
||||
cf_stream(doc, "<< /N 3 /Alternate [/Lab << /WhitePoint [0.9642 1 0.8249] >>] >>", nil))
|
||||
lab_icc, _ := colorspace_load(doc, alt, nil)
|
||||
if lab_icc == nil { return }
|
||||
testing.expect_value(t, lab_icc.n, 3)
|
||||
expect_rgb(t, colorspace_to_rgb(lab_icc, color_make(100, 0, 0)), 1, 1, 1, 1e-6,
|
||||
"rendered through the alternate")
|
||||
|
||||
// An /Alternate that contradicts /N is ignored in favour of /N.
|
||||
bad := cf_array(doc, Name("ICCBased"),
|
||||
cf_stream(doc, "<< /N 1 /Alternate /DeviceCMYK >>", nil))
|
||||
gray_icc, _ := colorspace_load(doc, bad, nil)
|
||||
if gray_icc == nil { return }
|
||||
testing.expect_value(t, gray_icc.n, 1)
|
||||
expect_rgb(t, colorspace_to_rgb(gray_icc, color_make(0.5)), 0.5, 0.5, 0.5, TOL,
|
||||
"one component is grey whatever the alternate said")
|
||||
|
||||
// Neither /N nor /Alternate: three components assumed, and a repair.
|
||||
guess := cf_array(doc, Name("ICCBased"), cf_stream(doc, "<< >>", nil))
|
||||
guessed, _ := colorspace_load(doc, guess, nil)
|
||||
if guessed != nil {
|
||||
testing.expect_value(t, guessed.n, 3)
|
||||
testing.expect(t, doc_has_repair(doc, .Bad_Resource), "guessing /N is a repair")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Indexed
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_indexed :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs, err := colorspace_load(doc, Ref{10, 0}, nil)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
if cs == nil { return }
|
||||
testing.expect_value(t, cs.kind, Colorspace_Kind.Indexed)
|
||||
// An Indexed colour has one component -- the index -- whatever the base is.
|
||||
testing.expect_value(t, cs.n, 1)
|
||||
testing.expect_value(t, cs.hival, 3)
|
||||
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 1, 0, 0, TOL, "entry 0")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 0, 1, 0, TOL, "entry 1")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(2)), 0, 0, 1, TOL, "entry 2")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(3)), 1, 1, 1, TOL, "entry 3")
|
||||
}
|
||||
|
||||
// An index outside 0..hival is clamped. It must never read past the table.
|
||||
@(test)
|
||||
test_colorspace_indexed_clamps :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs, _ := colorspace_load(doc, Ref{10, 0}, nil)
|
||||
if cs == nil { return }
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(4)), 1, 1, 1, TOL, "one past the end")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(9999)), 1, 1, 1, TOL, "far past the end")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(-1)), 1, 0, 0, TOL, "below the start")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(-9999)), 1, 0, 0, TOL, "far below")
|
||||
|
||||
// A table shorter than /HiVal claims: the entries that are not there read
|
||||
// as zero rather than off the end of the buffer.
|
||||
short := load_cs(t, doc, "[/Indexed /DeviceRGB 5 <FF0000>]")
|
||||
if short == nil { return }
|
||||
testing.expect_value(t, short.hival, 5)
|
||||
expect_rgb(t, colorspace_to_rgb(short, color_make(0)), 1, 0, 0, TOL, "the entry that exists")
|
||||
expect_rgb(t, colorspace_to_rgb(short, color_make(5)), 0, 0, 0, TOL, "one that does not")
|
||||
|
||||
// /HiVal above 255 is not a table this format can index.
|
||||
big := load_cs(t, doc, "[/Indexed /DeviceRGB 99999 <FF0000>]")
|
||||
if big != nil {
|
||||
testing.expect_value(t, big.hival, 255)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_indexed_variants :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
// The lookup table may be a stream instead of a string.
|
||||
stm := cf_stream(doc, "<< >>", []byte{0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF})
|
||||
cs, err := colorspace_load(doc,
|
||||
cf_array(doc, Name("Indexed"), Name("DeviceRGB"), i64(1), stm), nil)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
if cs == nil { return }
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 0, 0, 0, TOL, "stream entry 0")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 1, 1, TOL, "stream entry 1")
|
||||
|
||||
// A base whose components are not 0..1: each table byte spans the base
|
||||
// component's own range, so 255 in the L* slot is L* 100.
|
||||
lab := load_cs(t, doc,
|
||||
"[/Indexed [/Lab << /WhitePoint [0.9642 1 0.8249] /Range [-128 127] >>] " +
|
||||
"1 <008080 FF8080>]")
|
||||
if lab == nil { return }
|
||||
// 0x80 is the middle of [-128 127], which is a* = b* = -0.5: near neutral.
|
||||
expect_rgb(t, colorspace_to_rgb(lab, color_make(0)), 0, 0, 0, 0.01, "L* 0 entry")
|
||||
expect_rgb(t, colorspace_to_rgb(lab, color_make(1)), 1, 1, 1, 0.01, "L* 100 entry")
|
||||
|
||||
// An Indexed space over another Indexed space, or over a Pattern, means
|
||||
// nothing and is refused.
|
||||
_, e1 := colorspace_load(doc, cf_parse(doc, "[/Indexed [/Indexed /DeviceRGB 1 <0000>] 1 <0001>]"), nil)
|
||||
testing.expect_value(t, e1, Error.Bad_Colorspace)
|
||||
_, e2 := colorspace_load(doc, cf_parse(doc, "[/Indexed /Pattern 1 <0001>]"), nil)
|
||||
testing.expect_value(t, e2, Error.Bad_Colorspace)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Separation and DeviceN
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_separation :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
// A spot colour whose tint transform is a type 2 function into DeviceCMYK:
|
||||
// full tint is magenta plus yellow, which is red.
|
||||
cs, err := colorspace_load(doc, Ref{5, 0}, nil)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
if cs == nil { return }
|
||||
testing.expect_value(t, cs.kind, Colorspace_Kind.Separation)
|
||||
testing.expect_value(t, cs.n, 1)
|
||||
testing.expect(t, cs.tint != nil, "the tint transform loaded")
|
||||
testing.expect(t, cs.alt != nil && cs.alt.kind == .Device_CMYK, "the alternate space")
|
||||
testing.expect(t, len(cs.colorant_names) == 1 && cs.colorant_names[0] == "Spot", "colorant")
|
||||
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 1, 1, 1, TOL, "no tint is white")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 0, 0, TOL, "full tint is red")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.5)), 1, 0.5, 0.5, TOL, "half tint")
|
||||
|
||||
// The initial colour in a Separation is full tint, not zero (8.6.8).
|
||||
c := colorspace_default_color(cs)
|
||||
testing.expect_value(t, c.n, 1)
|
||||
testing.expect_value(t, c.v[0], 1)
|
||||
}
|
||||
|
||||
// /None marks nothing at all on the page.
|
||||
@(test)
|
||||
test_colorspace_separation_none :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := load_cs(t, doc,
|
||||
"[/Separation /None /DeviceCMYK << /FunctionType 2 /Domain [0 1] " +
|
||||
"/C0 [0 0 0 0] /C1 [0 0 0 1] /N 1 >>]")
|
||||
if cs == nil { return }
|
||||
testing.expect(t, cs.is_none, "/None paints nothing")
|
||||
testing.expect(t, !cs.is_all, "and is not /All")
|
||||
// Even at full tint, which would otherwise be solid black.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 1, 1, TOL, "nothing is painted")
|
||||
|
||||
all := load_cs(t, doc,
|
||||
"[/Separation /All /DeviceCMYK << /FunctionType 2 /Domain [0 1] " +
|
||||
"/C0 [0 0 0 0] /C1 [0 0 0 1] /N 1 >>]")
|
||||
if all == nil { return }
|
||||
testing.expect(t, all.is_all, "/All marks every separation")
|
||||
testing.expect(t, !all.is_none, "and is not /None")
|
||||
expect_rgb(t, colorspace_to_rgb(all, color_make(1)), 0, 0, 0, TOL, "full tint is black")
|
||||
}
|
||||
|
||||
// A tint transform that is missing or unusable must not lose the artwork: the
|
||||
// colorant is treated as ink coverage instead.
|
||||
@(test)
|
||||
test_colorspace_separation_broken_tint :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs := load_cs(t, doc, "[/Separation /Spot /DeviceCMYK << /FunctionType 9 >>]")
|
||||
if cs == nil { return }
|
||||
testing.expect(t, cs.tint == nil, "the tint transform did not load")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 1, 1, 1, TOL, "no ink")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 0, 0, 0, TOL, "full ink")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.25)), 0.75, 0.75, 0.75, TOL, "a quarter")
|
||||
testing.expect(t, doc_has_repair(doc, .Bad_Resource), "a broken tint is a repair")
|
||||
|
||||
// The alternate space missing entirely is survivable in the same way.
|
||||
no_alt := load_cs(t, doc, "[/Separation /Spot]")
|
||||
if no_alt != nil {
|
||||
expect_rgb(t, colorspace_to_rgb(no_alt, color_make(1)), 0, 0, 0, TOL, "no alternate")
|
||||
}
|
||||
}
|
||||
|
||||
// 8.6.6.4 and 8.6.6.5: the alternate "shall not be another special colour
|
||||
// space (Pattern, Indexed, Separation, or DeviceN)", and the four are excluded
|
||||
// for one reason -- a tint transform's output cannot be read as a colour in
|
||||
// any of them. Indexed is the one that shows: its single component is a
|
||||
// palette index, so a tint running 0..1 lands on entry 0 or entry 1 and the
|
||||
// whole ramp collapses onto two colours. Refusing the alternate drops the
|
||||
// space onto the ink-coverage fallback, which is monotone in the tint and
|
||||
// keeps the artwork.
|
||||
@(test)
|
||||
test_colorspace_separation_special_alternate :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
RAMP :: " << /FunctionType 2 /Domain [0 1] /C0 [0] /C1 [1] /N 1 >>"
|
||||
for src in ([]string{
|
||||
"[/Separation /Spot [/Indexed /DeviceRGB 3 <FF000000FF000000FFFFFFFF>]" + RAMP + "]",
|
||||
"[/Separation /Spot [/Separation /Other /DeviceGray" + RAMP + "]" + RAMP + "]",
|
||||
"[/Separation /Spot [/DeviceN [/A] /DeviceGray" + RAMP + "]" + RAMP + "]",
|
||||
"[/Separation /Spot /Pattern" + RAMP + "]",
|
||||
"[/DeviceN [/A] [/Indexed /DeviceRGB 3 <FF000000FF000000FFFFFFFF>]" + RAMP + "]",
|
||||
}) {
|
||||
cs := load_cs(t, doc, src)
|
||||
if cs == nil { continue }
|
||||
testing.expectf(t, cs.alt == nil, "%s: the alternate must be refused", src)
|
||||
testing.expectf(t, cs.tint == nil, "%s: and its tint transform with it", src)
|
||||
// Ink coverage, not two palette entries: the ramp runs the right way
|
||||
// and takes every value in between.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 1, 1, 1, TOL, "no ink")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.25)), 0.75, 0.75, 0.75, TOL, "a quarter")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.5)), 0.5, 0.5, 0.5, TOL, "half")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 0, 0, 0, TOL, "full ink")
|
||||
}
|
||||
testing.expect(t, doc_has_repair(doc, .Bad_Resource), "an unusable alternate is a repair")
|
||||
|
||||
// The control: a device alternate is what the rule allows, and it still
|
||||
// goes through the tint transform.
|
||||
good := load_cs(t, doc,
|
||||
"[/Separation /Spot /DeviceGray << /FunctionType 2 /Domain [0 1] " +
|
||||
"/C0 [1] /C1 [0] /N 1 >>]")
|
||||
if good == nil { return }
|
||||
testing.expect(t, good.alt != nil && good.alt.kind == .Device_Gray, "a device alternate loads")
|
||||
testing.expect(t, good.tint != nil, "and keeps its tint transform")
|
||||
expect_rgb(t, colorspace_to_rgb(good, color_make(0.25)), 0.75, 0.75, 0.75, TOL, "through the tint")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_devicen :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
// Two colorants into DeviceRGB. The program throws the second away and
|
||||
// copies the first into all three outputs.
|
||||
tint := cf_stream(doc,
|
||||
"<< /FunctionType 4 /Domain [0 1 0 1] /Range [0 1 0 1 0 1] >>",
|
||||
cf_bytes("{ pop dup dup }"))
|
||||
names := cf_array(doc, Name("Ink1"), Name("Ink2"))
|
||||
cs, err := colorspace_load(doc,
|
||||
cf_array(doc, Name("DeviceN"), names, Name("DeviceRGB"), tint), nil)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
if cs == nil { return }
|
||||
testing.expect_value(t, cs.kind, Colorspace_Kind.Device_N)
|
||||
testing.expect_value(t, cs.n, 2)
|
||||
testing.expect(t, cs.tint != nil, "the tint transform loaded")
|
||||
testing.expect(t, len(cs.colorant_names) == 2 && cs.colorant_names[1] == "Ink2", "colorants")
|
||||
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.25, 0.9)),
|
||||
0.25, 0.25, 0.25, TOL, "the first colorant drives all three outputs")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1, 0)), 1, 1, 1, TOL, "full first colorant")
|
||||
|
||||
// The initial colour is 1.0 in every component.
|
||||
c := colorspace_default_color(cs)
|
||||
testing.expect_value(t, c.n, 2)
|
||||
testing.expect_value(t, c.v[0], 1)
|
||||
testing.expect_value(t, c.v[1], 1)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_devicen_degrades :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
// No tint transform at all: the darkest colorant becomes ink coverage.
|
||||
names := cf_array(doc, Name("Ink1"), Name("Ink2"))
|
||||
cs, err := colorspace_load(doc,
|
||||
cf_array(doc, Name("DeviceN"), names, Name("DeviceRGB")), nil)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
if cs == nil { return }
|
||||
testing.expect_value(t, cs.n, 2)
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0, 0)), 1, 1, 1, TOL, "no ink")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0.25, 0.75)),
|
||||
0.25, 0.25, 0.25, TOL, "the darkest colorant")
|
||||
|
||||
// Every colorant /None: nothing is marked.
|
||||
nones := cf_array(doc, Name("None"), Name("None"))
|
||||
all_none, _ := colorspace_load(doc,
|
||||
cf_array(doc, Name("DeviceN"), nones, Name("DeviceRGB")), nil)
|
||||
if all_none != nil {
|
||||
testing.expect(t, all_none.is_none, "a DeviceN of nothing but /None marks nothing")
|
||||
}
|
||||
// One real colorant among them still marks.
|
||||
mixed := cf_array(doc, Name("None"), Name("Ink"))
|
||||
some, _ := colorspace_load(doc,
|
||||
cf_array(doc, Name("DeviceN"), mixed, Name("DeviceRGB")), nil)
|
||||
if some != nil {
|
||||
testing.expect(t, !some.is_none, "one real colorant is still painted")
|
||||
}
|
||||
|
||||
// A DeviceN with no colorants at all is not a colour space.
|
||||
_, e := colorspace_load(doc, cf_parse(doc, "[/DeviceN [] /DeviceRGB]"), nil)
|
||||
testing.expect_value(t, e, Error.Bad_Colorspace)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pattern
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_pattern :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
plain := load_cs(t, doc, "/Pattern")
|
||||
if plain == nil { return }
|
||||
testing.expect_value(t, plain.kind, Colorspace_Kind.Pattern)
|
||||
testing.expect(t, plain.under == nil, "a coloured pattern has no underlying space")
|
||||
// Nothing has been selected yet, so there are no components to carry --
|
||||
// `/P1 scn` is a name and no numbers, which is what cs.n has to say.
|
||||
testing.expect_value(t, plain.n, 0)
|
||||
testing.expect_value(t, colorspace_default_color(plain).n, 0)
|
||||
// And no image can be in a Pattern space (8.9.5.1), so it has no default
|
||||
// /Decode either.
|
||||
testing.expect(t, colorspace_default_decode(plain, 8, context.allocator) == nil,
|
||||
"a Pattern space has no image decode array")
|
||||
|
||||
// An uncoloured pattern carries the colour it is painted in.
|
||||
under := load_cs(t, doc, "[/Pattern /DeviceCMYK]")
|
||||
if under == nil { return }
|
||||
testing.expect_value(t, under.kind, Colorspace_Kind.Pattern)
|
||||
testing.expect_value(t, under.n, 4)
|
||||
testing.expect(t, under.under != nil && under.under.kind == .Device_CMYK, "underlying space")
|
||||
expect_rgb(t, colorspace_to_rgb(under, color_make(0, 1, 1, 0)), 1, 0, 0, TOL,
|
||||
"painted through the underlying space")
|
||||
// Whose initial colour is the underlying space's.
|
||||
c := colorspace_default_color(under)
|
||||
testing.expect_value(t, c.n, 4)
|
||||
testing.expect_value(t, c.v[3], 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resource lookup, caching and malformed input
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_named_through_resources :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
res, _ := to_dict(cf_parse(doc,
|
||||
"<< /ColorSpace << /CS0 [/Indexed /DeviceRGB 1 <FF000000FF00>] /CS1 /DeviceCMYK >> >>"))
|
||||
cs0 := load_cs(t, doc, "/CS0", res)
|
||||
if cs0 == nil { return }
|
||||
testing.expect_value(t, cs0.kind, Colorspace_Kind.Indexed)
|
||||
expect_rgb(t, colorspace_to_rgb(cs0, color_make(1)), 0, 1, 0, TOL, "resolved through /Resources")
|
||||
|
||||
cs1 := load_cs(t, doc, "/CS1", res)
|
||||
testing.expect(t, cs1 == colorspace_device_cmyk(doc), "a name for a device space")
|
||||
|
||||
// A caller holding only the /ColorSpace dictionary may pass that instead.
|
||||
csdict, _ := to_dict(cf_parse(doc, "<< /CSX /DeviceCMYK >>"))
|
||||
testing.expect(t, load_cs(t, doc, "/CSX", csdict) == colorspace_device_cmyk(doc),
|
||||
"the /ColorSpace dictionary works too")
|
||||
|
||||
// A name that is in no resource dictionary is not a colour space.
|
||||
_, err := colorspace_load(doc, cf_parse(doc, "/Nope"), res)
|
||||
testing.expect_value(t, err, Error.Bad_Colorspace)
|
||||
|
||||
// A device name cannot be redefined by a resource dictionary.
|
||||
shadow, _ := to_dict(cf_parse(doc, "<< /ColorSpace << /DeviceRGB /DeviceCMYK >> >>"))
|
||||
testing.expect(t, load_cs(t, doc, "/DeviceRGB", shadow) == colorspace_device_rgb(doc),
|
||||
"/DeviceRGB is always /DeviceRGB")
|
||||
}
|
||||
|
||||
// A page that names the same space in four hundred XObjects must build it once.
|
||||
@(test)
|
||||
test_colorspace_cached_by_object :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
a, _ := colorspace_load(doc, Ref{10, 0}, nil)
|
||||
b, _ := colorspace_load(doc, Ref{10, 0}, nil)
|
||||
testing.expect(t, a != nil && a == b, "the same object must build one space")
|
||||
|
||||
// Reached by name through a resource dictionary, the space behind the name
|
||||
// is the same object and so is cached as well.
|
||||
res, _ := to_dict(cf_parse(doc, "<< /ColorSpace << /CS0 10 0 R >> >>"))
|
||||
c := load_cs(t, doc, "/CS0", res)
|
||||
testing.expect(t, c == a, "the name resolves onto the cached space")
|
||||
|
||||
// A Separation loaded twice reuses the loaded tint transform, too.
|
||||
s1, _ := colorspace_load(doc, Ref{5, 0}, nil)
|
||||
s2, _ := colorspace_load(doc, Ref{5, 0}, nil)
|
||||
testing.expect(t, s1 != nil && s1 == s2, "a Separation is cached")
|
||||
}
|
||||
|
||||
// A colour space that names itself must stop rather than recursing forever.
|
||||
@(test)
|
||||
test_colorspace_self_reference :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
res, _ := to_dict(cf_parse(doc, "<< /ColorSpace << /Self 9 0 R >> >>"))
|
||||
cs, err := colorspace_load(doc, cf_parse(doc, "/Self"), res)
|
||||
testing.expect(t, cs == nil, "a self-naming space cannot be built")
|
||||
testing.expect_value(t, err, Error.Recursion_Limit)
|
||||
testing.expect(t, doc_has_repair(doc, .Recursion_Limit_Hit), "and it is a repair")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_rejects_nonsense :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
_, e1 := colorspace_load(doc, nil, nil)
|
||||
testing.expect_value(t, e1, Error.Bad_Colorspace)
|
||||
|
||||
_, e2 := colorspace_load(doc, cf_parse(doc, "42"), nil)
|
||||
testing.expect_value(t, e2, Error.Bad_Colorspace)
|
||||
|
||||
_, e3 := colorspace_load(doc, cf_parse(doc, "[]"), nil)
|
||||
testing.expect_value(t, e3, Error.Bad_Colorspace)
|
||||
|
||||
_, e4 := colorspace_load(doc, cf_parse(doc, "[/NotASpace 1 2 3]"), nil)
|
||||
testing.expect_value(t, e4, Error.Bad_Colorspace)
|
||||
|
||||
// Converting through no space at all is black, not a crash.
|
||||
expect_rgb(t, colorspace_to_rgb(nil, color_make(1, 1, 1)), 0, 0, 0, TOL, "nil space")
|
||||
testing.expect_value(t, colorspace_default_color(nil).n, 1)
|
||||
testing.expect(t, colorspace_default_decode(nil, 8) == nil, "nil space has no decode")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Initial colours and image decode arrays
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_colorspace_default_color :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
gray := colorspace_default_color(colorspace_device_gray(doc))
|
||||
testing.expect_value(t, gray.n, 1)
|
||||
testing.expect_value(t, gray.v[0], 0)
|
||||
|
||||
rgb := colorspace_default_color(colorspace_device_rgb(doc))
|
||||
testing.expect_value(t, rgb.n, 3)
|
||||
expect_rgb(t, colorspace_to_rgb(colorspace_device_rgb(doc), rgb), 0, 0, 0, TOL, "black")
|
||||
|
||||
// DeviceCMYK starts at 0 0 0 1 -- black -- not at four zeros, which would
|
||||
// be white (8.6.4.4).
|
||||
cmyk := colorspace_default_color(colorspace_device_cmyk(doc))
|
||||
testing.expect_value(t, cmyk.n, 4)
|
||||
testing.expect_value(t, cmyk.v[3], 1)
|
||||
expect_rgb(t, colorspace_to_rgb(colorspace_device_cmyk(doc), cmyk), 0, 0, 0, TOL, "black")
|
||||
|
||||
// Indexed starts at entry zero.
|
||||
idx, _ := colorspace_load(doc, Ref{10, 0}, nil)
|
||||
if idx != nil {
|
||||
c := colorspace_default_color(idx)
|
||||
testing.expect_value(t, c.n, 1)
|
||||
testing.expect_value(t, c.v[0], 0)
|
||||
}
|
||||
|
||||
// Lab starts at 0 0 0, which is black.
|
||||
lab := load_cs(t, doc, "[/Lab << /WhitePoint [0.9642 1 0.8249] >>]")
|
||||
if lab != nil {
|
||||
c := colorspace_default_color(lab)
|
||||
testing.expect_value(t, c.n, 3)
|
||||
expect_rgb(t, colorspace_to_rgb(lab, c), 0, 0, 0, TOL, "black")
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_colorspace_default_decode :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
gray := colorspace_default_decode(colorspace_device_gray(doc), 8)
|
||||
defer delete(gray)
|
||||
testing.expect_value(t, len(gray), 2)
|
||||
testing.expect_value(t, gray[0], 0)
|
||||
testing.expect_value(t, gray[1], 1)
|
||||
|
||||
cmyk := colorspace_default_decode(colorspace_device_cmyk(doc), 8)
|
||||
defer delete(cmyk)
|
||||
testing.expect_value(t, len(cmyk), 8)
|
||||
testing.expect_value(t, cmyk[7], 1)
|
||||
|
||||
// Indexed maps the raw sample straight onto the table, so its default
|
||||
// depends on the bit depth.
|
||||
idx, _ := colorspace_load(doc, Ref{10, 0}, nil)
|
||||
if idx != nil {
|
||||
Depth :: struct { bpc: int, want: f64 }
|
||||
depths := []Depth{
|
||||
Depth{1, 1}, Depth{2, 3}, Depth{4, 15}, Depth{8, 255}, Depth{16, 65535},
|
||||
}
|
||||
for c in depths {
|
||||
d := colorspace_default_decode(idx, c.bpc)
|
||||
defer delete(d)
|
||||
testing.expect_value(t, len(d), 2)
|
||||
testing.expect_value(t, d[0], 0)
|
||||
testing.expectf(t, d[1] == c.want, "bpc %d: got %f, want %f", c.bpc, d[1], c.want)
|
||||
}
|
||||
}
|
||||
|
||||
// Lab's default decode is its own component ranges.
|
||||
lab := load_cs(t, doc,
|
||||
"[/Lab << /WhitePoint [0.9642 1 0.8249] /Range [-90 80 -70 60] >>]")
|
||||
if lab != nil {
|
||||
d := colorspace_default_decode(lab, 8)
|
||||
defer delete(d)
|
||||
testing.expect_value(t, len(d), 6)
|
||||
testing.expect_value(t, d[0], 0)
|
||||
testing.expect_value(t, d[1], 100)
|
||||
testing.expect_value(t, d[2], -90)
|
||||
testing.expect_value(t, d[3], 80)
|
||||
testing.expect_value(t, d[4], -70)
|
||||
testing.expect_value(t, d[5], 60)
|
||||
}
|
||||
|
||||
// A Separation's decode is one 0..1 pair, whatever its alternate space is.
|
||||
sep, _ := colorspace_load(doc, Ref{5, 0}, nil)
|
||||
if sep != nil {
|
||||
d := colorspace_default_decode(sep, 8)
|
||||
defer delete(d)
|
||||
testing.expect_value(t, len(d), 2)
|
||||
testing.expect_value(t, d[1], 1)
|
||||
}
|
||||
}
|
||||
|
||||
// A colour space nested inside another still converts: an Indexed table whose
|
||||
// entries are spot-colour tints.
|
||||
@(test)
|
||||
test_colorspace_nested :: proc(t: ^testing.T) {
|
||||
doc := cf_open(t)
|
||||
defer cf_close(t, doc)
|
||||
|
||||
cs, err := colorspace_load(doc,
|
||||
cf_array(doc, Name("Indexed"), Ref{5, 0}, i64(1), String(transmute([]byte)string("\x00\xff"))),
|
||||
nil)
|
||||
testing.expect_value(t, err, Error.None)
|
||||
if cs == nil { return }
|
||||
testing.expect(t, cs.base != nil && cs.base.kind == .Separation, "the base is the spot colour")
|
||||
// Entry 0 is no tint at all, entry 1 is full tint: white, then red.
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(0)), 1, 1, 1, TOL, "entry 0")
|
||||
expect_rgb(t, colorspace_to_rgb(cs, color_make(1)), 1, 0, 0, TOL, "entry 1")
|
||||
}
|
||||
Reference in New Issue
Block a user