openpencil/tests/engine/render/canvas/image-fill.test.ts
2026-09-13 13:48:11 +03:00

121 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, mock, test } from 'bun:test'
import type { Fill, SceneNode } from '@open-pencil/scene-graph'
import { makeImageFillLocalMatrix, patternTileLayout } from '#core/canvas/fills'
import type { SkiaRenderer } from '#core/canvas/renderer'
function createRenderer() {
return {
ck: {
Matrix: {
identity: mock(() => ['identity']),
invert: mock((matrix) => ['invert', matrix]),
multiply: mock((...matrices) => ['multiply', ...matrices]),
scaled: mock((x, y) => ['scaled', x, y]),
translated: mock((x, y) => ['translated', x, y])
}
}
} as SkiaRenderer
}
const node = {
width: 120,
height: 80
} as SceneNode
describe('canvas pattern fills', () => {
test('uses scale, spacing, and alignment for rectangular pattern tiles', () => {
const layout = patternTileLayout(
{ width: 20, height: 10 } as SceneNode,
{
type: 'PATTERN',
scale: 2,
patternSpacing: { x: 0.25, y: 0.5 },
horizontalAlignment: 'CENTER',
verticalAlignment: 'END',
color: { r: 0, g: 0, b: 0, a: 1 },
opacity: 1,
visible: true
} as Fill
)
expect(layout).toEqual({
rect: { x: 0, y: 0, width: 50, height: 30 },
scale: 2,
positions: [{ x: -5, y: -10 }]
})
})
test('adds an offset source copy for hexagonal pattern tiles', () => {
const layout = patternTileLayout(
{ width: 20, height: 10 } as SceneNode,
{
type: 'PATTERN',
patternTileType: 'HORIZONTAL_HEXAGONAL',
patternSpacing: { x: 0, y: 0 },
color: { r: 0, g: 0, b: 0, a: 1 },
opacity: 1,
visible: true
} as Fill
)
expect(layout.positions).toEqual([
{ x: 0, y: 0 },
{ x: 10, y: 5 }
])
})
})
describe('canvas image fills', () => {
test.each([
{ width: 100, height: 100, scale: 0.8, x: 20, y: 0 },
{ width: 240, height: 80, scale: 0.5, x: 0, y: 20 },
{ width: 60, height: 40, scale: 2, x: 0, y: 0 }
])(
'centers FIT images without stretching ($width × $height)',
({ width, height, scale, x, y }) => {
const renderer = createRenderer()
const fill = { type: 'IMAGE', imageScaleMode: 'FIT' } as Fill
expect(makeImageFillLocalMatrix(renderer, fill, node, width, height)).toEqual([
'multiply',
['translated', x, y],
['scaled', scale, scale]
])
}
)
test('keeps untransformed tile fills in image pixel space', () => {
const renderer = createRenderer()
const fill = { type: 'IMAGE', imageScaleMode: 'TILE' } as Fill
const matrix = makeImageFillLocalMatrix(renderer, fill, node, 24, 16)
expect(matrix).toEqual(['identity'])
expect(renderer.ck.Matrix.identity).toHaveBeenCalled()
expect(renderer.ck.Matrix.scaled).not.toHaveBeenCalled()
})
test('uses the full imported affine transform for patterned image fills', () => {
const renderer = createRenderer()
const transform = { m00: 0.5, m01: 0.1, m02: 0.25, m10: -0.2, m11: 0.25, m12: 0.5 }
const fill = {
type: 'IMAGE',
imageScaleMode: 'TILE',
imageTransform: transform
} as Fill
const matrix = makeImageFillLocalMatrix(renderer, fill, node, 40, 40)
expect(renderer.ck.Matrix.invert).toHaveBeenCalledWith([
0.5, 0.1, 0.25, -0.2, 0.25, 0.5, 0, 0, 1
])
expect(matrix).toEqual([
'multiply',
['scaled', 120, 80],
['invert', [0.5, 0.1, 0.25, -0.2, 0.25, 0.5, 0, 0, 1]],
['scaled', 0.025, 0.025]
])
})
})