Use native paragraph foreground paints instead of independent outline layout, keeping mutable shader paragraphs transient. Render transformed diamond gradients with a retained, owned runtime program and align Skia surface encoding with the browser drawing buffer. Add independent pixel and ownership regressions and correct only the reviewed text, gradient and FIT-image visual oracles. Existing arrow/blur snapshot failures and the separate 84-pixel comparison remain unresolved; no tolerances are relaxed.
120 lines
4 KiB
TypeScript
120 lines
4 KiB
TypeScript
import { expect, test } from 'bun:test'
|
|
|
|
import type { Fill } from '@open-pencil/scene-graph'
|
|
import { SceneGraph } from '@open-pencil/scene-graph'
|
|
|
|
import { initCanvasKit } from '#cli/headless'
|
|
import { SkiaRenderer } from '#core/canvas/renderer'
|
|
|
|
import { expectDefined } from '#tests/helpers/assert'
|
|
|
|
test('diamond contours use Manhattan distance and dispose their retained program', async () => {
|
|
const ck = await initCanvasKit()
|
|
const surface = expectDefined(ck.MakeSurface(101, 101), 'surface')
|
|
const renderer = new SkiaRenderer(ck, surface)
|
|
const graph = new SceneGraph()
|
|
const page = expectDefined(graph.getPages()[0], 'page')
|
|
const fill: Fill = {
|
|
type: 'GRADIENT_DIAMOND',
|
|
color: { r: 0, g: 0, b: 0, a: 1 },
|
|
opacity: 1,
|
|
visible: true,
|
|
gradientStops: [
|
|
{ position: 0, color: { r: 0, g: 0, b: 0, a: 1 } },
|
|
{ position: 1, color: { r: 1, g: 1, b: 1, a: 1 } }
|
|
],
|
|
gradientTransform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 }
|
|
}
|
|
const node = graph.createNode('RECTANGLE', page.id, { width: 101, height: 101, fills: [fill] })
|
|
let effect = renderer.diamondGradientEffect
|
|
try {
|
|
const draw = () => {
|
|
const canvas = surface.getCanvas()
|
|
canvas.clear(ck.TRANSPARENT)
|
|
renderer.renderShape(canvas, node, graph)
|
|
surface.flush()
|
|
}
|
|
draw()
|
|
effect = renderer.diamondGradientEffect
|
|
draw()
|
|
expect(renderer.diamondGradientEffect).toBe(effect)
|
|
const image = surface.makeImageSnapshot()
|
|
try {
|
|
const pixels = expectDefined(
|
|
image.readPixels(0, 0, {
|
|
width: 101,
|
|
height: 101,
|
|
colorType: ck.ColorType.RGBA_8888,
|
|
alphaType: ck.AlphaType.Unpremul,
|
|
colorSpace: ck.ColorSpace.SRGB
|
|
}),
|
|
'pixels'
|
|
)
|
|
const red = (x: number, y: number) => pixels[(y * 101 + x) * 4]
|
|
expect(red(50, 50)).toBe(0)
|
|
expect(red(75, 75)).toBe(red(100, 50))
|
|
expect(red(75, 75)).toBeGreaterThan(245)
|
|
expect(red(0, 0)).toBe(255)
|
|
effect = expectDefined(effect, 'compiled effect')
|
|
} finally {
|
|
image.delete()
|
|
}
|
|
} finally {
|
|
renderer.destroy()
|
|
}
|
|
expect(effect?.isDeleted()).toBe(true)
|
|
expect(renderer.diamondGradientEffect).toBeNull()
|
|
})
|
|
|
|
test('diamond gradients preserve transformed contours and intermediate stops', async () => {
|
|
const ck = await initCanvasKit()
|
|
const renderer = new SkiaRenderer(ck, expectDefined(ck.MakeSurface(100, 100), 'surface'))
|
|
const graph = new SceneGraph()
|
|
const page = expectDefined(graph.getPages()[0], 'page')
|
|
const black = { r: 0, g: 0, b: 0, a: 1 }
|
|
const fill: Fill = {
|
|
type: 'GRADIENT_DIAMOND',
|
|
color: black,
|
|
opacity: 1,
|
|
visible: true,
|
|
gradientTransform: { m00: 0, m01: -1, m02: 1.005, m10: 0.5, m11: 0, m12: 0.255 },
|
|
gradientStops: [
|
|
{ position: 0, color: black },
|
|
{ position: 0.5, color: { r: 1, g: 0, b: 0, a: 1 } },
|
|
{ position: 1, color: { r: 1, g: 1, b: 1, a: 1 } }
|
|
]
|
|
}
|
|
const node = graph.createNode('RECTANGLE', page.id, { width: 100, height: 100, fills: [fill] })
|
|
try {
|
|
const canvas = renderer.surface.getCanvas()
|
|
canvas.clear(ck.TRANSPARENT)
|
|
renderer.renderShape(canvas, node, graph)
|
|
renderer.surface.flush()
|
|
const image = renderer.surface.makeImageSnapshot()
|
|
try {
|
|
const pixels = expectDefined(
|
|
image.readPixels(0, 0, {
|
|
width: 100,
|
|
height: 100,
|
|
colorType: ck.ColorType.RGBA_8888,
|
|
alphaType: ck.AlphaType.Unpremul,
|
|
colorSpace: ck.ColorSpace.SRGB
|
|
}),
|
|
'pixels'
|
|
)
|
|
const pixel = (x: number, y: number) =>
|
|
Array.from(pixels.slice((y * 100 + x) * 4, (y * 100 + x) * 4 + 4))
|
|
expect(pixel(50, 50)).toEqual([0, 0, 0, 255])
|
|
expect(pixel(50, 56)).toEqual([122, 0, 0, 255])
|
|
expect(pixel(62, 50)).toEqual(pixel(50, 56))
|
|
expect(pixel(50, 63)).toEqual([255, 10, 10, 255])
|
|
expect(pixel(76, 50)).toEqual(pixel(50, 63))
|
|
expect(pixel(50, 75)).toEqual([255, 255, 255, 255])
|
|
} finally {
|
|
image.delete()
|
|
}
|
|
} finally {
|
|
renderer.destroy()
|
|
}
|
|
})
|