Merge pull request #215 from guzba/master

underline, strikethrough, insertion sort for hits is faster, context.globalAlpha
This commit is contained in:
treeform 2021-06-01 13:09:52 -07:00 committed by GitHub
commit 0dfc513351
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 401 additions and 121 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -108,16 +108,26 @@ proc main() =
# Figure out the right size of the window we want. # Figure out the right size of the window we want.
var rect: lean.RECT var rect: lean.RECT
rect.left = 0 rect.left = 0
rect.top = 0 rect.top = 0
rect.right = w rect.right = w
rect.bottom = h rect.bottom = h
AdjustWindowRectEx(cast[LPRECT](rect.addr), WS_OVERLAPPEDWINDOW, 0, 0) AdjustWindowRectEx(cast[LPRECT](rect.addr), WS_OVERLAPPEDWINDOW, 0, 0)
# Open the window. # Open the window.
hwnd = CreateWindow(appName, "Win32/Pixie", WS_OVERLAPPEDWINDOW, hwnd = CreateWindow(
CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, appName,
0, 0, hInstance, nil) "Win32/Pixie",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
0,
0,
hInstance,
nil
)
ShowWindow(hwnd, SW_SHOW) ShowWindow(hwnd, SW_SHOW)
UpdateWindow(hwnd) UpdateWindow(hwnd)

View file

@ -1,4 +1,4 @@
import pixie, wNim/[wApp, wFrame, wPaintDC, wImage] import pixie, wNim/wApp, wNim/wFrame, wNim/wImage, wNim/wPaintDC
let let
w = 256 w = 256

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -10,6 +10,7 @@ type
Context* = ref object Context* = ref object
image*: Image image*: Image
fillStyle*, strokeStyle*: Paint fillStyle*, strokeStyle*: Paint
globalAlpha*: float32
lineWidth*: float32 lineWidth*: float32
miterLimit*: float32 miterLimit*: float32
lineCap*: LineCap lineCap*: LineCap
@ -25,6 +26,7 @@ type
ContextState = object ContextState = object
fillStyle, strokeStyle: Paint fillStyle, strokeStyle: Paint
globalAlpha: float32
lineWidth: float32 lineWidth: float32
miterLimit: float32 miterLimit: float32
lineCap: LineCap lineCap: LineCap
@ -44,6 +46,7 @@ proc newContext*(image: Image): Context =
result = Context() result = Context()
result.image = image result.image = image
result.mat = mat3() result.mat = mat3()
result.globalAlpha = 1
result.lineWidth = 1 result.lineWidth = 1
result.miterLimit = 10 result.miterLimit = 10
result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
@ -56,6 +59,7 @@ proc newContext*(width, height: int): Context {.inline.} =
proc state(ctx: Context): ContextState = proc state(ctx: Context): ContextState =
result.fillStyle = ctx.fillStyle result.fillStyle = ctx.fillStyle
result.strokeStyle = ctx.strokeStyle result.strokeStyle = ctx.strokeStyle
result.globalAlpha = ctx.globalAlpha
result.lineWidth = ctx.lineWidth result.lineWidth = ctx.lineWidth
result.miterLimit = ctx.miterLimit result.miterLimit = ctx.miterLimit
result.lineCap = ctx.lineCap result.lineCap = ctx.lineCap
@ -94,6 +98,7 @@ proc restore*(ctx: Context) =
let state = ctx.stateStack.pop() let state = ctx.stateStack.pop()
ctx.fillStyle = state.fillStyle ctx.fillStyle = state.fillStyle
ctx.strokeStyle = state.strokeStyle ctx.strokeStyle = state.strokeStyle
ctx.globalAlpha = state.globalAlpha
ctx.lineWidth = state.lineWidth ctx.lineWidth = state.lineWidth
ctx.miterLimit = state.miterLimit ctx.miterLimit = state.miterLimit
ctx.lineCap = state.lineCap ctx.lineCap = state.lineCap
@ -113,9 +118,13 @@ proc restore*(ctx: Context) =
else: # Otherwise draw to the root image else: # Otherwise draw to the root image
ctx.image.draw(poppedLayer) ctx.image.draw(poppedLayer)
proc fill( proc fill(ctx: Context, image: Image, path: Path, windingRule: WindingRule) =
ctx: Context, image: Image, path: Path, windingRule: WindingRule var image = image
) {.inline.} =
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.fillPath( image.fillPath(
path, path,
ctx.fillStyle, ctx.fillStyle,
@ -123,7 +132,17 @@ proc fill(
windingRule windingRule
) )
proc stroke(ctx: Context, image: Image, path: Path) {.inline.} = if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc stroke(ctx: Context, image: Image, path: Path) =
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.strokePath( image.strokePath(
path, path,
ctx.strokeStyle, ctx.strokeStyle,
@ -135,6 +154,10 @@ proc stroke(ctx: Context, image: Image, path: Path) {.inline.} =
ctx.lineDash ctx.lineDash
) )
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} = proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
if ctx.font.typeface == nil: if ctx.font.typeface == nil:
raise newException(PixieError, "No font has been set on this Context") raise newException(PixieError, "No font has been set on this Context")
@ -145,6 +168,12 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
ctx.font.paint = ctx.fillStyle ctx.font.paint = ctx.fillStyle
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.fillText( image.fillText(
ctx.font, ctx.font,
text, text,
@ -152,6 +181,10 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
hAlign = ctx.textAlign hAlign = ctx.textAlign
) )
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} = proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
if ctx.font.typeface == nil: if ctx.font.typeface == nil:
raise newException(PixieError, "No font has been set on this Context") raise newException(PixieError, "No font has been set on this Context")
@ -162,6 +195,12 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
ctx.font.paint = ctx.strokeStyle ctx.font.paint = ctx.strokeStyle
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.strokeText( image.strokeText(
ctx.font, ctx.font,
text, text,
@ -174,6 +213,10 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
dashes = ctx.lineDash dashes = ctx.lineDash
) )
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc beginPath*(ctx: Context) {.inline.} = proc beginPath*(ctx: Context) {.inline.} =
## Starts a new path by emptying the list of sub-paths. ## Starts a new path by emptying the list of sub-paths.
ctx.path = Path() ctx.path = Path()

View file

@ -304,11 +304,11 @@ type
lookupList: LookupList lookupList: LookupList
PostTable = ref object PostTable = ref object
version: float32 version*: float32
italicAngle: float32 italicAngle*: float32
underlinePosition: int16 underlinePosition*: int16
underlineThickness: int16 underlineThickness*: int16
isFixedPitch: uint32 isFixedPitch*: uint32
OpenType* = ref object OpenType* = ref object
buf*: string buf*: string

View file

@ -18,6 +18,8 @@ type
lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height. lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height.
paint*: Paint paint*: Paint
textCase*: TextCase textCase*: TextCase
underline*: bool ## Apply an underline.
strikethrough*: bool ## Apply a strikethrough.
noKerningAdjustments*: bool ## Optionally disable kerning pair adjustments noKerningAdjustments*: bool ## Optionally disable kerning pair adjustments
Span* = ref object Span* = ref object
@ -25,10 +27,11 @@ type
font*: Font font*: Font
Arrangement* = ref object Arrangement* = ref object
spans*: seq[(int, int)] ## The (start, stop) of the spans in the text. lines*: seq[(int, int)] ## The (start, stop) of the lines of text.
fonts*: seq[Font] ## The font for each span. spans*: seq[(int, int)] ## The (start, stop) of the spans in the text.
runes*: seq[Rune] ## The runes of the text. fonts*: seq[Font] ## The font for each span.
positions*: seq[Vec2] ## The positions of the glyphs for each rune. runes*: seq[Rune] ## The runes of the text.
positions*: seq[Vec2] ## The positions of the glyphs for each rune.
selectionRects*: seq[Rect] ## The selection rects for each glyph. selectionRects*: seq[Rect] ## The selection rects for each glyph.
HAlignMode* = enum HAlignMode* = enum
@ -72,6 +75,22 @@ proc lineHeight*(typeface: Typeface): float32 {.inline.} =
## The default line height in font units. ## The default line height in font units.
typeface.ascent - typeface.descent + typeface.lineGap typeface.ascent - typeface.descent + typeface.lineGap
proc underlinePosition(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.post.underlinePosition.float32
proc underlineThickness(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.post.underlineThickness.float32
proc strikeoutPosition(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.os2.yStrikeoutPosition.float32
proc strikeoutThickness(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.os2.yStrikeoutSize.float32
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} = proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
## The glyph path for the rune. ## The glyph path for the rune.
if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu) if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu)
@ -177,7 +196,7 @@ proc typeset*(
result.positions.setLen(result.runes.len) result.positions.setLen(result.runes.len)
result.selectionRects.setLen(result.runes.len) result.selectionRects.setLen(result.runes.len)
var lines = @[(0, 0)] # (start, stop) result.lines = @[(0, 0)] # (start, stop)
block: # Arrange the glyphs horizontally first (handling line breaks) block: # Arrange the glyphs horizontally first (handling line breaks)
proc advance(font: Font, runes: seq[Rune], i: int): float32 {.inline.} = proc advance(font: Font, runes: seq[Rune], i: int): float32 {.inline.} =
@ -200,10 +219,10 @@ proc typeset*(
at.x = 0 at.x = 0
at.y += 1 at.y += 1
prevCanWrap = 0 prevCanWrap = 0
lines[^1][1] = runeIndex result.lines[^1][1] = runeIndex
# Start a new line if we are not at the end # Start a new line if we are not at the end
if runeIndex + 1 < result.runes.len: if runeIndex + 1 < result.runes.len:
lines.add((runeIndex + 1, 0)) result.lines.add((runeIndex + 1, 0))
else: else:
let advance = advance(font, result.runes, runeIndex) let advance = advance(font, result.runes, runeIndex)
if wrap and rune != SP and bounds.x > 0 and at.x + advance > bounds.x: if wrap and rune != SP and bounds.x > 0 and at.x + advance > bounds.x:
@ -221,8 +240,8 @@ proc typeset*(
at.x += advance(font, result.runes, i) at.x += advance(font, result.runes, i)
dec lineStart dec lineStart
lines[^1][1] = lineStart - 1 result.lines[^1][1] = lineStart - 1
lines.add((lineStart, 0)) result.lines.add((lineStart, 0))
if rune.canWrap(): if rune.canWrap():
prevCanWrap = runeIndex prevCanWrap = runeIndex
@ -231,12 +250,12 @@ proc typeset*(
result.selectionRects[runeIndex] = rect(at.x, at.y, advance, 0) result.selectionRects[runeIndex] = rect(at.x, at.y, advance, 0)
at.x += advance at.x += advance
lines[^1][1] = result.runes.len - 1 result.lines[^1][1] = result.runes.len - 1
if hAlign != haLeft: if hAlign != haLeft:
# Since horizontal alignment adjustments are different for each line, # Since horizontal alignment adjustments are different for each line,
# find the start and stop of each line of text. # find the start and stop of each line of text.
for (start, stop) in lines: for (start, stop) in result.lines:
var furthestX: float32 var furthestX: float32
for i in countdown(stop, start): for i in countdown(stop, start):
if result.runes[i] != SP and result.runes[i] != LF: if result.runes[i] != SP and result.runes[i] != LF:
@ -289,11 +308,11 @@ proc typeset*(
) / 2 ) / 2
maxInitialY = max(maxInitialY, round(fontUnitInitialY * font.scale)) maxInitialY = max(maxInitialY, round(fontUnitInitialY * font.scale))
for runeIndex in start .. stop: for runeIndex in start .. stop:
if runeIndex == lines[0][1]: if runeIndex == result.lines[0][1]:
break outer break outer
maxInitialY maxInitialY
var lineHeights = newSeq[float32](lines.len) var lineHeights = newSeq[float32](result.lines.len)
block: # Compute each line's line height block: # Compute each line's line height
var line: int var line: int
for spanIndex, (start, stop) in result.spans: for spanIndex, (start, stop) in result.spans:
@ -306,11 +325,12 @@ proc typeset*(
font.defaultLineHeight font.defaultLineHeight
lineHeights[line] = max(lineHeights[line], fontLineHeight) lineHeights[line] = max(lineHeights[line], fontLineHeight)
for runeIndex in start .. stop: for runeIndex in start .. stop:
if line + 1 < lines.len and runeIndex == lines[line + 1][0]: if line + 1 < result.lines.len and
runeIndex == result.lines[line + 1][0]:
inc line inc line
lineHeights[line] = max(lineHeights[line], fontLineHeight) lineHeights[line] = max(lineHeights[line], fontLineHeight)
# Handle when span and line endings coincide # Handle when span and line endings coincide
if line + 1 < lines.len and stop == lines[line][1]: if line + 1 < result.lines.len and stop == result.lines[line][1]:
inc line inc line
block: # Vertically position the glyphs block: # Vertically position the glyphs
@ -326,7 +346,8 @@ proc typeset*(
else: else:
font.defaultLineHeight font.defaultLineHeight
for runeIndex in start .. stop: for runeIndex in start .. stop:
if line + 1 < lines.len and runeIndex == lines[line + 1][0]: if line + 1 < result.lines.len and
runeIndex == result.lines[line + 1][0]:
inc line inc line
baseline += lineHeights[line] baseline += lineHeights[line]
result.positions[runeIndex].y = baseline result.positions[runeIndex].y = baseline
@ -404,24 +425,96 @@ proc parseSvgFont*(buf: string): Font =
result.lineHeight = AutoLineHeight result.lineHeight = AutoLineHeight
result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
proc textUber(
target: Image | Mask,
arrangement: Arrangement,
transform: Vec2 | Mat3 = vec2(0, 0),
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[],
stroke: static[bool] = false
) =
var line: int
for spanIndex, (start, stop) in arrangement.spans:
let
font = arrangement.fonts[spanIndex]
underlineThickness = font.typeface.underlineThickness * font.scale
underlinePosition = font.typeface.underlinePosition * font.scale
strikeoutThickness = font.typeface.strikeoutThickness * font.scale
strikeoutPosition = font.typeface.strikeoutPosition * font.scale
for runeIndex in start .. stop:
let position = arrangement.positions[runeIndex]
var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex])
path.transform(
translate(position) *
scale(vec2(font.scale))
)
var applyDecoration = true
if runeIndex == arrangement.lines[line][1]:
inc line
if arrangement.runes[runeIndex] == SP:
# Do not apply decoration to the space at end of lines
applyDecoration = false
if applyDecoration:
if font.underline:
path.rect(
arrangement.selectionRects[runeIndex].x,
position.y - underlinePosition + underlineThickness / 2,
arrangement.selectionRects[runeIndex].w,
underlineThickness
)
if font.strikethrough:
path.rect(
arrangement.selectionRects[runeIndex].x,
position.y - strikeoutPosition,
arrangement.selectionRects[runeIndex].w,
strikeoutThickness
)
when stroke:
when type(target) is Image:
target.strokePath(
path,
font.paint,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else: # target is Mask
target.strokePath(
path,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else:
when type(target) is Image:
target.fillPath(path, font.paint, transform)
else: # target is Mask
target.fillPath(path, transform)
proc fillText*( proc fillText*(
target: Image | Mask, target: Image | Mask,
arrangement: Arrangement, arrangement: Arrangement,
transform: Vec2 | Mat3 = vec2(0, 0) transform: Vec2 | Mat3 = vec2(0, 0)
) = ) {.inline.} =
## Fills the text arrangement. ## Fills the text arrangement.
for spanIndex, (start, stop) in arrangement.spans: textUber(
let font = arrangement.fonts[spanIndex] target,
for runeIndex in start .. stop: arrangement,
var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) transform
path.transform( )
translate(arrangement.positions[runeIndex]) *
scale(vec2(font.scale))
)
when type(target) is Image:
target.fillPath(path, font.paint, transform)
else: # target is Mask
target.fillPath(path, transform)
proc fillText*( proc fillText*(
target: Image | Mask, target: Image | Mask,
@ -448,37 +541,19 @@ proc strokeText*(
lineJoin = ljMiter, lineJoin = ljMiter,
miterLimit = defaultMiterLimit, miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[] dashes: seq[float32] = @[]
) = ) {.inline.} =
## Strokes the text arrangement. ## Strokes the text arrangement.
for spanIndex, (start, stop) in arrangement.spans: textUber(
let font = arrangement.fonts[spanIndex] target,
for runeIndex in start .. stop: arrangement,
var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) transform,
path.transform( strokeWidth,
translate(arrangement.positions[runeIndex]) * lineCap,
scale(vec2(font.scale)) lineJoin,
) miterLimit,
when type(target) is Image: dashes,
target.strokePath( true
path, )
font.paint,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else: # target is Mask
target.strokePath(
path,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
proc strokeText*( proc strokeText*(
target: Image | Mask, target: Image | Mask,

View file

@ -807,7 +807,7 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
of Move: of Move:
if shape.len > 0: if shape.len > 0:
result.add(shape) result.add(shape)
shape.setLen(0) shape = newSeq[Vec2]()
at.x = command.numbers[0] at.x = command.numbers[0]
at.y = command.numbers[1] at.y = command.numbers[1]
start = at start = at
@ -881,7 +881,7 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
of RMove: of RMove:
if shape.len > 0: if shape.len > 0:
result.add(shape) result.add(shape)
shape.setLen(0) shape = newSeq[Vec2]()
at.x += command.numbers[0] at.x += command.numbers[0]
at.y += command.numbers[1] at.y += command.numbers[1]
start = at start = at
@ -959,7 +959,7 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
at = start at = start
if shape.len > 0: if shape.len > 0:
result.add(shape) result.add(shape)
shape.setLen(0) shape = newSeq[Vec2]()
prevCommandKind = command.kind prevCommandKind = command.kind
@ -995,8 +995,8 @@ proc computePixelBounds(segments: seq[(Segment, int16)]): Rect =
for (segment, _) in segments: for (segment, _) in segments:
xMin = min(xMin, min(segment.at.x, segment.to.x)) xMin = min(xMin, min(segment.at.x, segment.to.x))
xMax = max(xMax, max(segment.at.x, segment.to.x)) xMax = max(xMax, max(segment.at.x, segment.to.x))
yMin = min(yMin, min(segment.at.y, segment.to.y)) yMin = min(yMin, segment.at.y)
yMax = max(yMax, max(segment.at.y, segment.to.y)) yMax = max(yMax, segment.to.y)
xMin = floor(xMin) xMin = floor(xMin)
xMax = ceil(xMax) xMax = ceil(xMax)
@ -1071,6 +1071,16 @@ proc quickSort(a: var seq[(float32, int16)], inl, inr: int) =
quickSort(a, inl, r) quickSort(a, inl, r)
quickSort(a, l, inr) quickSort(a, l, inr)
proc insertionSort(s: var seq[(float32, int16)], hi: int) {.inline.} =
for i in 1 .. hi:
var
j = i - 1
k = i
while j >= 0 and s[j][0] > s[k][0]:
swap(s[j + 1], s[j])
dec j
dec k
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} = proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
## Should we fill based on the current winding rule and count? ## Should we fill based on the current winding rule and count?
case windingRule: case windingRule:
@ -1113,7 +1123,10 @@ template computeCoverages(
hits[numHits] = (min(at.x, size.x), winding) hits[numHits] = (min(at.x, size.x), winding)
inc numHits inc numHits
quickSort(hits, 0, numHits - 1) if hits.len > 32:
quickSort(hits, 0, numHits - 1)
else:
insertionSort(hits, numHits - 1)
var var
prevAt: float32 prevAt: float32
@ -1166,7 +1179,7 @@ template computeCoverages(
when defined(pixieLeakCheck): when defined(pixieLeakCheck):
if prevAt != size.x and count != 0: if prevAt != size.x and count != 0:
echo "Leak detected: ", count, " @ ", prevAt, " ", y echo "Leak detected: ", count, " @ (", prevAt, ", ", y, ")"
proc fillShapes( proc fillShapes(
image: Image, image: Image,
@ -1184,15 +1197,14 @@ proc fillShapes(
bounds = computePixelBounds(segments) bounds = computePixelBounds(segments)
startX = max(0, bounds.x.int) startX = max(0, bounds.x.int)
startY = max(0, bounds.y.int) startY = max(0, bounds.y.int)
stopY = min(image.height, (bounds.y + bounds.h).int) pathHeight = min(image.height, (bounds.y + bounds.h).int)
pathHeight = stopY - startY partitions = partitionSegments(segments, startY, pathHeight - startY)
partitions = partitionSegments(segments, startY, pathHeight)
var var
coverages = newSeq[uint8](image.width) coverages = newSeq[uint8](image.width)
hits = newSeq[(float32, int16)](4) hits = newSeq[(float32, int16)](4)
for y in startY ..< stopY: for y in startY ..< pathHeight:
computeCoverages( computeCoverages(
coverages, coverages,
hits, hits,

View file

@ -1,16 +0,0 @@
import benchy, pixie
const paragraph = "She had come to the conclusion that you could tell a lot about a person by their ears. The way they stuck out and the size of the earlobes could give you wonderful insights into the person. Of course, she couldn't scientifically prove any of this, but that didn't matter to her. Before anything else, she would size up the ears of the person she was talking to. She's asked the question so many times that she barely listened to the answers anymore. The answers were always the same. Well, not exactly the same, but the same in a general sense. A more accurate description was the answers never surprised her."
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 16
let image = newImage(500, 300)
timeIt "paragraph":
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
paragraph,
bounds = image.wh
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -1,4 +1,4 @@
import pixie, random, os import os, pixie, random
when not defined(pixieLeakCheck): when not defined(pixieLeakCheck):
quit("Requires -d:pixieLeakCheck") quit("Requires -d:pixieLeakCheck")

View file

@ -1,4 +1,4 @@
import pixie, random, pixie/fileformats/svg import pixie, pixie/fileformats/svg, random
when not defined(pixieLeakCheck): when not defined(pixieLeakCheck):
quit("Requires -d:pixieLeakCheck") quit("Requires -d:pixieLeakCheck")
@ -8,7 +8,7 @@ randomize()
let data = readFile("tests/images/svg/Ghostscript_Tiger.svg") let data = readFile("tests/images/svg/Ghostscript_Tiger.svg")
for i in 0 ..< 100_000: for i in 0 ..< 100_000:
var image = decodeSvg(data, rand(300 .. 1800), rand(30 .. 1800)) var image = decodeSvg(data, rand(300 .. 1800), rand(30 .. 1800))
# image.writeFile("tests/fuzz_leaks3.png") # image.writeFile("tests/fuzz_leaks3.png")
# break # break

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 KiB

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

View file

@ -499,20 +499,20 @@ block:
var y = 15.float32 var y = 15.float32
proc drawDashedLine(pattern: seq[float32]) = proc drawDashedLine(pattern: seq[float32]) =
ctx.beginPath(); ctx.beginPath()
ctx.setLineDash(pattern); ctx.setLineDash(pattern)
ctx.moveTo(0, y); ctx.moveTo(0, y)
ctx.lineTo(300, y); ctx.lineTo(300, y)
ctx.stroke(); ctx.stroke()
y += 20; y += 20
drawDashedLine(@[]); drawDashedLine(@[])
drawDashedLine(@[1.float32, 1]); drawDashedLine(@[1.float32, 1])
drawDashedLine(@[10.float32, 10]); drawDashedLine(@[10.float32, 10])
drawDashedLine(@[20.float32, 5]); drawDashedLine(@[20.float32, 5])
drawDashedLine(@[15.float32, 3, 3, 3]); drawDashedLine(@[15.float32, 3, 3, 3])
drawDashedLine(@[20.float32, 3, 3, 3, 3, 3, 3, 3]); drawDashedLine(@[20.float32, 3, 3, 3, 3, 3, 3, 3])
drawDashedLine(@[12.float32, 3, 3]); drawDashedLine(@[12.float32, 3, 3])
image.writeFile("tests/images/context/setLineDash_1.png") image.writeFile("tests/images/context/setLineDash_1.png")
@ -531,3 +531,20 @@ block:
ctx.fillRect(10, 10, 100, 100) ctx.fillRect(10, 10, 100, 100)
image.writeFile("tests/images/context/blendmode_1.png") image.writeFile("tests/images/context/blendmode_1.png")
block:
let
image = newImage(300, 150)
ctx = newContext(image)
image.fill(rgba(255, 255, 255, 255))
ctx.globalAlpha = 0.5
ctx.fillStyle = "blue"
ctx.fillRect(10, 10, 100, 100)
ctx.fillStyle = "red"
ctx.fillRect(50, 50, 100, 100)
image.writeFile("tests/images/context/globalAlpha_1.png")

View file

@ -781,18 +781,18 @@ block:
var font1 = ubuntu var font1 = ubuntu
font1.size = 15 font1.size = 15
font1.paint.color = parseHtmlColor("#CACACA").rgba() font1.paint = "#CACACA"
var font2 = ubuntu var font2 = ubuntu
font2.size = 84 font2.size = 84
var font3 = ubuntu var font3 = ubuntu
font3.size = 18 font3.size = 18
font3.paint.color = parseHtmlColor("#007FF4").rgba() font3.paint = "#007FF4"
var font4 = ubuntu var font4 = ubuntu
font4.size = 20 font4.size = 20
font4.paint.color = parseHtmlColor("#4F4F4F").rgba() font4.paint = "#4F4F4F"
let spans = @[ let spans = @[
newSpan("verb [with object] ", font1), newSpan("verb [with object] ", font1),
@ -809,3 +809,142 @@ block:
image.fillText(arrangement, vec2(20, 20)) image.fillText(arrangement, vec2(20, 20))
doDiff(image, "spans5") doDiff(image, "spans5")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.underline = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "underline1")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.underline = true
font.paint = rgba(0, 0, 0, 127)
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "underline2")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.underline = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.strokeText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "underline3")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.strikethrough = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "strikethrough1")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.strikethrough = true
font.paint = rgba(0, 0, 0, 127)
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "strikethrough2")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.strikethrough = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.strokeText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "strikethrough3")
block:
let ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
var font1 = ubuntu
font1.size = 15
font1.paint = "#CACACA"
var font2 = ubuntu
font2.size = 84
var font3 = ubuntu
font3.size = 18
font3.paint = "#007FF4"
var font4 = ubuntu
font4.size = 20
font4.paint = "#4F4F4F"
var font5 = ubuntu
font5.size = 20
font5.paint = "#4F4F4F"
font5.underline = true
var font6 = ubuntu
font6.size = 20
font6.paint = "#4F4F4F"
font6.strikethrough = true
let spans = @[
newSpan("verb [with object] ", font1),
newSpan("strallow\n", font2),
newSpan("\nstral·low\n", font3),
newSpan("\n1. free (something) from ", font4),
newSpan("restrictive restrictions", font5),
newSpan(" ", font4),
newSpan("\"the regulations are intended to strallow changes in public policy\" ", font6)
]
let image = newImage(400, 400)
image.fill(rgba(255, 255, 255, 255))
let arrangement = typeset(spans, bounds = vec2(360, 360))
image.fillText(arrangement, vec2(20, 20))
doDiff(image, "spans6")