Merge pull request from treeform/guzba

delete mask ref object type
This commit is contained in:
Andre von Houck 2022-07-26 20:05:54 -07:00 committed by GitHub
commit 0ad674fc29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 379 additions and 2145 deletions

View file

@ -223,9 +223,10 @@ mask.fillPath(
Q 180 120 100 180
Q 20 120 20 60
z
"""
""",
color(1, 1, 1, 1)
)
lines.draw(mask)
lines.draw(mask, blendMode = MaskBlend)
image.draw(lines)
```
![example output](examples/masking.png)
@ -303,8 +304,8 @@ nim c -r [examples/blur.nim](examples/blur.nim)
let path = newPath()
path.polygon(vec2(100, 100), 70, sides = 6)
let mask = newMask(200, 200)
mask.fillPath(path)
let mask = newImage(200, 200)
mask.fillPath(path, color(1, 1, 1, 1))
blur.blur(20)
blur.draw(mask, blendMode = MaskBlend)

View file

@ -125,12 +125,10 @@ exportRefObject Image:
applyOpacity(Image, float32)
invert(Image)
blur(Image, float32, Color)
newMask(Image)
resize(Image, int, int)
shadow(Image, Vec2, float32, float32, Color)
superImage
draw(Image, Image, Mat3, BlendMode)
draw(Image, Mask, Mat3, BlendMode)
fillGradient
fillText(Image, Font, string, Mat3, Vec2, HorizontalAlignment, VerticalAlignment)
fillText(Image, Arrangement, Mat3)
@ -140,36 +138,6 @@ exportRefObject Image:
strokePath(Image, Path, Paint, Mat3, float32, LineCap, LineJoin, float32, seq[float32])
newContext(Image)
exportRefObject Mask:
fields:
width
height
constructor:
newMask(int, int)
procs:
writeFile(Mask, string)
copy(Mask)
getValue
setValue
fill(Mask, uint8)
minifyBy2(Mask, int)
magnifyBy2(Mask, int)
spread
ceil(Mask)
newImage(Mask)
applyOpacity(Mask, float32)
invert(Mask)
blur(Mask, float32, uint8)
resize(Mask, int, int)
draw(Mask, Mask, Mat3, BlendMode)
draw(Mask, Image, Mat3, BlendMode)
fillText(Mask, Font, string, Mat3, Vec2, HorizontalAlignment, VerticalAlignment)
fillText(Mask, Arrangement, Mat3)
strokeText(Mask, Font, string, Mat3, float32, Vec2, HorizontalAlignment, VerticalAlignment, LineCap, LineJoin, float32, seq[float32])
strokeText(Mask, Arrangement, Mat3, float32, LineCap, LineJoin, float32, seq[float32])
fillPath(Mask, Path, Mat3, WindingRule)
strokePath(Mask, Path, Mat3, float32, LineCap, LineJoin, float32, seq[float32])
exportRefObject Paint:
fields:
kind
@ -320,7 +288,6 @@ exportProcs:
decodeImageDimensions
readImage
readImageDimensions
readmask
readTypeface
readFont
parsePath

View file

@ -10,8 +10,8 @@ image.fill(rgba(255, 255, 255, 255))
let path = newPath()
path.polygon(vec2(100, 100), 70, sides = 6)
let mask = newMask(200, 200)
mask.fillPath(path)
let mask = newImage(200, 200)
mask.fillPath(path, color(1, 1, 1, 1))
blur.blur(20)
blur.draw(mask, blendMode = MaskBlend)

Binary file not shown.

Before

(image error) Size: 88 KiB

After

(image error) Size: 91 KiB

Binary file not shown.

Before

(image error) Size: 10 KiB

After

(image error) Size: 9.3 KiB

Binary file not shown.

Before

(image error) Size: 4.6 KiB

After

(image error) Size: 3.6 KiB

Binary file not shown.

Before

(image error) Size: 40 KiB

After

(image error) Size: 40 KiB

Binary file not shown.

Before

(image error) Size: 2.4 KiB

After

(image error) Size: 1.3 KiB

View file

@ -3,7 +3,7 @@ import pixie
let
image = newImage(200, 200)
lines = newImage(200, 200)
mask = newMask(200, 200)
mask = newImage(200, 200)
lines.fill(parseHtmlColor("#FC427B").rgba)
image.fill(rgba(255, 255, 255, 255))
@ -23,9 +23,10 @@ mask.fillPath(
Q 180 120 100 180
Q 20 120 20 60
z
"""
""",
color(1, 1, 1, 1)
)
lines.draw(mask)
lines.draw(mask, blendMode = MaskBlend)
image.draw(lines)
image.writeFile("examples/masking.png")

Binary file not shown.

Before

(image error) Size: 5.9 KiB

After

(image error) Size: 4.8 KiB

Binary file not shown.

Before

(image error) Size: 2.2 KiB

After

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 9.2 KiB

After

(image error) Size: 9.5 KiB

Binary file not shown.

Before

(image error) Size: 914 B

After

(image error) Size: 629 B

Binary file not shown.

Before

(image error) Size: 20 KiB

After

(image error) Size: 17 KiB

Binary file not shown.

Before

(image error) Size: 18 KiB

After

(image error) Size: 15 KiB

Binary file not shown.

Before

(image error) Size: 42 KiB

After

(image error) Size: 40 KiB

View file

@ -1,10 +1,10 @@
import bumpy, chroma, flatty/binny, os, pixie/common, pixie/contexts,
pixie/fileformats/bmp, pixie/fileformats/gif, pixie/fileformats/jpeg,
pixie/fileformats/png, pixie/fileformats/ppm, pixie/fileformats/qoi,
pixie/fileformats/svg, pixie/fonts, pixie/images, pixie/internal, pixie/masks, pixie/paints,
pixie/paths, strutils, vmath
pixie/fileformats/svg, pixie/fonts, pixie/images, pixie/internal,
pixie/paints, pixie/paths, strutils, vmath
export bumpy, chroma, common, contexts, fonts, images, masks, paints, paths, vmath
export bumpy, chroma, common, contexts, fonts, images, paints, paths, vmath
type
FileFormat* = enum
@ -57,13 +57,6 @@ proc decodeImage*(data: string): Image {.raises: [PixieError].} =
else:
raise newException(PixieError, "Unsupported image file format")
proc decodeMask*(data: string): Mask {.raises: [PixieError].} =
## Loads a mask from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
newMask(decodePng(data).convertToImage())
else:
raise newException(PixieError, "Unsupported mask file format")
proc readImageDimensions*(
filePath: string
): ImageDimensions {.inline, raises: [PixieError].} =
@ -80,13 +73,6 @@ proc readImage*(filePath: string): Image {.inline, raises: [PixieError].} =
except IOError as e:
raise newException(PixieError, e.msg, e)
proc readMask*(filePath: string): Mask {.raises: [PixieError].} =
## Loads a mask from a file.
try:
decodeMask(readFile(filePath))
except IOError as e:
raise newException(PixieError, e.msg, e)
proc encodeImage*(image: Image, fileFormat: FileFormat): string {.raises: [PixieError].} =
## Encodes an image into memory.
case fileFormat:
@ -103,14 +89,6 @@ proc encodeImage*(image: Image, fileFormat: FileFormat): string {.raises: [Pixie
of PpmFormat:
image.encodePpm()
proc encodeMask*(mask: Mask, fileFormat: FileFormat): string {.raises: [PixieError].} =
## Encodes a mask into memory.
case fileFormat:
of PngFormat:
mask.encodePng()
else:
raise newException(PixieError, "Unsupported file format")
proc writeFile*(image: Image, filePath: string) {.raises: [PixieError].} =
## Writes an image to a file.
let fileFormat = case splitFile(filePath).ext.toLowerAscii():
@ -127,22 +105,6 @@ proc writeFile*(image: Image, filePath: string) {.raises: [PixieError].} =
except IOError as e:
raise newException(PixieError, e.msg, e)
proc writeFile*(mask: Mask, filePath: string) {.raises: [PixieError].} =
## Writes a mask to a file.
let fileFormat = case splitFile(filePath).ext.toLowerAscii():
of ".png": PngFormat
of ".bmp": BmpFormat
of ".jpg", ".jpeg": JpegFormat
of ".qoi": QoiFormat
of ".ppm": PpmFormat
else:
raise newException(PixieError, "Unsupported file extension")
try:
writeFile(filePath, mask.encodeMask(fileFormat))
except IOError as e:
raise newException(PixieError, e.msg, e)
proc fill*(image: Image, paint: Paint) {.raises: [PixieError].} =
## Fills the image with the paint.
case paint.kind:

View file

@ -8,8 +8,6 @@ import chroma, common, simd, std/math
type
Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX {.gcsafe, raises: [].}
## Function signature returned by blender.
MaskBlender* = proc(backdrop, source: uint8): uint8 {.gcsafe, raises: [].}
## Function signature returned by maskBlender.
when defined(release):
{.push checks: off.}
@ -414,54 +412,10 @@ proc blender*(blendMode: BlendMode): Blender {.raises: [].} =
of SubtractMaskBlend: subtractMaskBlender
of ExcludeMaskBlend: excludeMaskBlender
proc maskBlendNormal*(backdrop, source: uint8): uint8 {.inline.} =
## Normal blend masks
blendAlpha(backdrop, source)
proc maskBlendMask*(backdrop, source: uint8): uint8 {.inline.} =
## Mask blend masks
((backdrop.uint32 * source) div 255).uint8
proc maskBlendSubtract*(backdrop, source: uint8): uint8 {.inline.} =
## Subtract blend masks
((backdrop.uint32 * (255 - source)) div 255).uint8
proc maskBlendExclude*(backdrop, source: uint8): uint8 {.inline.} =
## Exclude blend masks
max(backdrop, source) - min(backdrop, source)
proc maskBlendNormalMaskBlender(backdrop, source: uint8): uint8 =
maskBlendNormal(backdrop, source)
proc maskBlendMaskMaskBlender(backdrop, source: uint8): uint8 =
maskBlendMask(backdrop, source)
proc maskBlendSubtractMaskBlender(backdrop, source: uint8): uint8 =
maskBlendSubtract(backdrop, source)
proc maskBlendExcludeMaskBlender(backdrop, source: uint8): uint8 =
maskBlendExclude(backdrop, source)
proc maskBlendOverwriteMaskBlender(backdrop, source: uint8): uint8 =
source
proc maskBlender*(blendMode: BlendMode): MaskBlender {.raises: [PixieError].} =
## Returns a blend masking function for a given blend masking mode.
case blendMode:
of NormalBlend: maskBlendNormalMaskBlender
of MaskBlend: maskBlendMaskMaskBlender
of OverwriteBlend: maskBlendOverwriteMaskBlender
of SubtractMaskBlend: maskBlendSubtractMaskBlender
of ExcludeMaskBlend: maskBlendExcludeMaskBlender
else:
raise newException(PixieError, "No masker for " & $blendMode)
when defined(amd64) and allowSimd:
type
BlenderSimd* = proc(blackdrop, source: M128i): M128i {.gcsafe, raises: [].}
## Function signature returned by blenderSimd.
MaskerSimd* = proc(blackdrop, source: M128i): M128i {.gcsafe, raises: [].}
## Function signature returned by maskerSimd.
proc blendNormalSimd*(backdrop, source: M128i): M128i {.inline.} =
let
@ -535,112 +489,5 @@ when defined(amd64) and allowSimd:
## Is there a blend function for a given blend mode with SIMD support?
blendMode in {NormalBlend, MaskBlend, OverwriteBlend}
proc maskBlendNormalSimd*(backdrop, source: M128i): M128i {.inline.} =
## Blending masks
let
oddMask = mm_set1_epi16(cast[int16](0xff00))
v255high = mm_set1_epi16(cast[int16](255.uint16 shl 8))
div255 = mm_set1_epi16(cast[int16](0x8081))
var
sourceEven = mm_slli_epi16(source, 8)
sourceOdd = mm_and_si128(source, oddMask)
let
evenK = mm_sub_epi16(v255high, sourceEven)
oddK = mm_sub_epi16(v255high, sourceOdd)
var
backdropEven = mm_slli_epi16(backdrop, 8)
backdropOdd = mm_and_si128(backdrop, oddMask)
backdropEven = mm_mulhi_epu16(backdropEven, evenK)
backdropOdd = mm_mulhi_epu16(backdropOdd, oddK)
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
sourceEven = mm_srli_epi16(sourceEven, 8)
sourceOdd = mm_srli_epi16(sourceOdd, 8)
let
blendedEven = mm_add_epi16(sourceEven, backdropEven)
blendedOdd = mm_add_epi16(sourceOdd, backdropOdd)
mm_or_si128(blendedEven, mm_slli_epi16(blendedOdd, 8))
proc maskBlendMaskSimd*(backdrop, source: M128i): M128i =
let
oddMask = mm_set1_epi16(cast[int16](0xff00))
div255 = mm_set1_epi16(cast[int16](0x8081))
sourceEven = mm_slli_epi16(source, 8)
sourceOdd = mm_and_si128(source, oddMask)
var
backdropEven = mm_slli_epi16(backdrop, 8)
backdropOdd = mm_and_si128(backdrop, oddMask)
backdropEven = mm_mulhi_epu16(backdropEven, sourceEven)
backdropOdd = mm_mulhi_epu16(backdropOdd, sourceOdd)
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
proc maskBlendSubtractSimd*(backdrop, source: M128i): M128i {.inline.} =
let
oddMask = mm_set1_epi16(cast[int16](0xff00))
vec255 = mm_set1_epi8(255)
div255 = mm_set1_epi16(cast[int16](0x8081))
let sourceMinus255 = mm_sub_epi8(vec255, source)
var
multiplierEven = mm_slli_epi16(sourceMinus255, 8)
multiplierOdd = mm_and_si128(sourceMinus255, oddMask)
backdropEven = mm_slli_epi16(backdrop, 8)
backdropOdd = mm_and_si128(backdrop, oddMask)
backdropEven = mm_mulhi_epu16(backdropEven, multiplierEven)
backdropOdd = mm_mulhi_epu16(backdropOdd, multiplierOdd)
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
proc maskBlendExcludeSimd*(backdrop, source: M128i): M128i {.inline.} =
mm_sub_epi8(mm_max_epu8(backdrop, source), mm_min_epu8(backdrop, source))
proc maskBlendNormalSimdMaskBlender(backdrop, source: M128i): M128i =
maskBlendNormalSimd(backdrop, source)
proc maskBlendMaskSimdMaskBlender(backdrop, source: M128i): M128i =
maskBlendMaskSimd(backdrop, source)
proc maskBlendExcludeSimdMaskBlender(backdrop, source: M128i): M128i =
maskBlendExcludeSimd(backdrop, source)
proc maskBlendSubtractSimdMaskBlender(backdrop, source: M128i): M128i =
maskBlendSubtractSimd(backdrop, source)
proc maskBlenderSimd*(blendMode: BlendMode): MaskerSimd {.raises: [PixieError].} =
## Returns a blend masking function with SIMD support.
case blendMode:
of NormalBlend: maskBlendNormalSimdMaskBlender
of MaskBlend: maskBlendMaskSimdMaskBlender
of OverwriteBlend: overwriteSimdBlender
of SubtractMaskBlend: maskBlendSubtractSimdMaskBlender
of ExcludeMaskBlend: maskBlendExcludeSimdMaskBlender
else:
raise newException(PixieError, "No SIMD masker for " & $blendMode)
proc hasSimdMaskBlender*(blendMode: BlendMode): bool {.inline, raises: [].} =
## Is there a blend masking function with SIMD support?
blendMode in {
NormalBlend,
MaskBlend,
OverwriteBlend,
SubtractMaskBlend,
ExcludeMaskBlend
}
when defined(release):
{.pop.}

View file

@ -36,11 +36,6 @@ type
width*, height*: int
data*: seq[ColorRGBX]
Mask* = ref object
## Mask object that holds mask opacity data.
width*, height*: int
data*: seq[uint8]
proc newImage*(width, height: int): Image {.raises: [PixieError].} =
## Creates a new image with the parameter dimensions.
if width <= 0 or height <= 0:
@ -51,16 +46,6 @@ proc newImage*(width, height: int): Image {.raises: [PixieError].} =
result.height = height
result.data = newSeq[ColorRGBX](width * height)
proc newMask*(width, height: int): Mask {.raises: [PixieError].} =
## Creates a new mask with the parameter dimensions.
if width <= 0 or height <= 0:
raise newException(PixieError, "Mask width and height must be > 0")
result = Mask()
result.width = width
result.height = height
result.data = newSeq[uint8](width * height)
proc mix*(a, b: uint8, t: float32): uint8 {.inline, raises: [].} =
## Linearly interpolate between a and b using t.
let t = round(t * 255).uint32

View file

@ -1,5 +1,5 @@
import bumpy, chroma, pixie/common, pixie/fonts, pixie/images, pixie/masks,
pixie/paints, pixie/paths, tables, vmath
import bumpy, chroma, pixie/common, pixie/fonts, pixie/images, pixie/paints,
pixie/paths, tables, vmath
## This file provides a Nim version of the Canvas 2D API commonly used on the
## web. The goal is to make picking up Pixie easy for developers familiar with
@ -30,8 +30,7 @@ type
lineDash: seq[float32]
path: Path
mat: Mat3
mask: Mask
layer: Image
mask, layer: Image
stateStack: seq[ContextState]
typefaces: Table[string, Typeface]
@ -47,8 +46,7 @@ type
textAlign: HorizontalAlignment
lineDash: seq[float32]
mat: Mat3
mask: Mask
layer: Image
mask, layer: Image
TextMetrics* = object
width*: float32
@ -133,7 +131,7 @@ proc restore*(ctx: Context) {.raises: [PixieError].} =
if poppedLayer != nil: # If there is a layer being popped
if poppedMask != nil: # If there is a mask, apply it
poppedLayer.draw(poppedMask)
poppedLayer.draw(poppedMask, blendMode = MaskBlend)
if ctx.layer != nil: # If we popped to another layer, draw to it
ctx.layer.draw(poppedLayer)
else: # Otherwise draw to the root image
@ -391,10 +389,16 @@ proc clip*(
## region, if any, is intersected with the current or given path to create
## the new clipping region.
if ctx.mask == nil:
ctx.mask = newMask(ctx.image.width, ctx.image.height)
ctx.mask.fillPath(path, windingRule = windingRule)
ctx.mask = newImage(ctx.image.width, ctx.image.height)
let maskPaint = newPaint(SolidPaint)
maskPaint.color = color(1, 1, 1, 1)
maskPaint.blendMode = OverwriteBlend
ctx.mask.fillPath(path, maskPaint, windingRule = windingRule)
else:
ctx.mask.fillPath(path, windingRule = windingRule, blendMode = MaskBlend)
let maskPaint = newPaint(SolidPaint)
maskPaint.color = color(1, 1, 1, 1)
maskPaint.blendMode = MaskBlend
ctx.mask.fillPath(path, maskPaint, windingRule = windingRule)
proc clip*(
ctx: Context, windingRule = NonZero

View file

@ -388,7 +388,7 @@ proc decodeGifDimensions*(
result.width = data.readInt16(6).int
result.height = data.readInt16(8).int
proc newImage*(gif: Gif): Image {.raises: [PixieError].} =
proc newImage*(gif: Gif): Image {.raises: [].} =
gif.frames[0].copy()
when defined(release):

View file

@ -1,5 +1,5 @@
import chroma, flatty/binny, pixie/common, pixie/images, pixie/internal,
pixie/masks, pixie/simd, std/decls, std/sequtils, std/strutils
pixie/simd, std/decls, std/sequtils, std/strutils
# This JPEG decoder is loosely based on stb_image which is public domain.
@ -59,7 +59,7 @@ type
widthCoeff, heightCoeff: int
coeff, lineBuf: seq[uint8]
blocks: seq[seq[array[64, int16]]]
channel: Mask
channel: seq[uint8]
DecoderState = object
buffer: ptr UncheckedArray[uint8]
@ -321,7 +321,8 @@ proc decodeSOF0(state: var DecoderState) =
component.widthStride = state.numMcuWide * component.yScale * 8
component.heightStride = state.numMcuHigh * component.xScale * 8
component.channel = newMask(component.widthStride, component.heightStride)
component.channel =
newSeq[uint8](component.widthStride * component.heightStride)
if state.progressive:
component.widthCoeff = component.widthStride div 8
@ -812,14 +813,14 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) =
x2 += 65536 + (128 shl 17)
x3 += 65536 + (128 shl 17)
component.channel.data[outPos + 0] = clampByte((x0 + t3) shr 17)
component.channel.data[outPos + 7] = clampByte((x0 - t3) shr 17)
component.channel.data[outPos + 1] = clampByte((x1 + t2) shr 17)
component.channel.data[outPos + 6] = clampByte((x1 - t2) shr 17)
component.channel.data[outPos + 2] = clampByte((x2 + t1) shr 17)
component.channel.data[outPos + 5] = clampByte((x2 - t1) shr 17)
component.channel.data[outPos + 3] = clampByte((x3 + t0) shr 17)
component.channel.data[outPos + 4] = clampByte((x3 - t0) shr 17)
component.channel[outPos + 0] = clampByte((x0 + t3) shr 17)
component.channel[outPos + 7] = clampByte((x0 - t3) shr 17)
component.channel[outPos + 1] = clampByte((x1 + t2) shr 17)
component.channel[outPos + 6] = clampByte((x1 - t2) shr 17)
component.channel[outPos + 2] = clampByte((x2 + t1) shr 17)
component.channel[outPos + 5] = clampByte((x2 - t1) shr 17)
component.channel[outPos + 3] = clampByte((x3 + t0) shr 17)
component.channel[outPos + 4] = clampByte((x3 - t0) shr 17)
{.pop.}
@ -901,45 +902,59 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
data
)
proc magnifyXBy2(mask: Mask): Mask =
## Smooth magnify by power of 2 only in the X direction.
result = newMask(mask.width * 2, mask.height)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
let n = 3 * mask.unsafe[x, y].uint16
if x == 0:
result.unsafe[x * 2 + 0, y] = mask.unsafe[x, y]
result.unsafe[x * 2 + 1, y] =
((n + mask.unsafe[x + 1, y].uint16 + 2) div 4).uint8
elif x == mask.width - 1:
result.unsafe[x * 2 + 0, y] =
((n + mask.unsafe[x - 1, y].uint16 + 2) div 4).uint8
result.unsafe[x * 2 + 1, y] = mask.unsafe[x, y]
else:
result.unsafe[x * 2 + 0, y] =
((n + mask.unsafe[x - 1, y].uint16) div 4).uint8
result.unsafe[x * 2 + 1, y] =
((n + mask.unsafe[x + 1, y].uint16) div 4).uint8
template dataIndex(component: var Component, x, y: int): int =
component.widthStride * y + x
proc magnifyYBy2(mask: Mask): Mask =
## Smooth magnify by power of 2 only in the Y direction.
result = newMask(mask.width, mask.height * 2)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
let n = 3 * mask.unsafe[x, y].uint16
if y == 0:
result.unsafe[x, y * 2 + 0] = mask.unsafe[x, y]
result.unsafe[x, y * 2 + 1] =
((n + mask.unsafe[x, y + 1].uint16 + 2) div 4).uint8
elif y == mask.height - 1:
result.unsafe[x, y * 2 + 0] =
((n + mask.unsafe[x, y - 1].uint16 + 2) div 4).uint8
result.unsafe[x, y * 2 + 1] = mask.unsafe[x, y]
template `[]`*(component: Component, x, y: int): uint8 =
component.channel[component.dataIndex(x, y)]
proc magnifyXBy2(component: var Component) =
## Smooth magnify by power of 2 only in the X direction.
var magnified =
newSeq[uint8](component.widthStride * 2 * component.heightStride)
for y in 0 ..< component.heightStride:
for x in 0 ..< component.widthStride:
let n = 3 * component[x, y].uint16
if x == 0:
magnified[component.dataIndex(x * 2 + 0, y)] = component[x, y]
magnified[component.dataIndex(x * 2 + 1, y)] =
((n + component[x + 1, y].uint16 + 2) div 4).uint8
elif x == component.widthStride - 1:
magnified[component.dataIndex(x * 2 + 0, y)] =
((n + component[x - 1, y].uint16 + 2) div 4).uint8
magnified[component.dataIndex(x * 2 + 1, y)] = component[x, y]
else:
result.unsafe[x, y * 2 + 0] =
((n + mask.unsafe[x, y - 1].uint16) div 4).uint8
result.unsafe[x, y * 2 + 1] =
((n + mask.unsafe[x, y + 1].uint16) div 4).uint8
magnified[component.dataIndex(x * 2 + 0, y)] =
((n + component[x - 1, y].uint16) div 4).uint8
magnified[component.dataIndex(x * 2 + 1, y)] =
((n + component[x + 1, y].uint16) div 4).uint8
component.channel = move magnified
component.widthStride *= 2
proc magnifyYBy2(component: var Component) =
## Smooth magnify by power of 2 only in the Y direction.
var magnified =
newSeq[uint8](component.widthStride * component.heightStride * 2)
for y in 0 ..< component.heightStride:
for x in 0 ..< component.widthStride:
let n = 3 * component[x, y].uint16
if y == 0:
magnified[component.dataIndex(x, y * 2 + 0)] = component[x, y]
magnified[component.dataIndex(x, y * 2 + 1)] =
((n + component[x, y + 1].uint16 + 2) div 4).uint8
elif y == component.heightStride - 1:
magnified[component.dataIndex(x, y * 2 + 0)] =
((n + component[x, y - 1].uint16 + 2) div 4).uint8
magnified[component.dataIndex(x, y * 2 + 1)] = component[x, y]
else:
magnified[component.dataIndex(x, y * 2 + 0)] =
((n + component[x, y - 1].uint16) div 4).uint8
magnified[component.dataIndex(x, y * 2 + 1)] =
((n + component[x, y + 1].uint16) div 4).uint8
component.channel = move magnified
component.heightStride *= 2
proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX =
## Takes a 3 component yCbCr outputs and populates image.
@ -974,33 +989,29 @@ proc buildImage(state: var DecoderState): Image =
of 3:
for component in state.components.mitems:
while component.yScale < state.maxYScale:
component.channel = component.channel.magnifyXBy2()
component.magnifyXBy2()
component.yScale *= 2
while component.xScale < state.maxXScale:
component.channel = component.channel.magnifyYBy2()
component.magnifyYBy2()
component.xScale *= 2
let
cy = state.components[0].channel
cb = state.components[1].channel
cr = state.components[2].channel
for y in 0 ..< state.imageHeight:
var channelIndex = cy.dataIndex(0, y)
var channelIndex = state.components[0].dataIndex(0, y)
for x in 0 ..< state.imageWidth:
result.unsafe[x, y] = yCbCrToRgbx(
cy.data[channelIndex],
cb.data[channelIndex],
cr.data[channelIndex],
state.components[0].channel[channelIndex], # cy
state.components[1].channel[channelIndex], # cb
state.components[2].channel[channelIndex], # cr
)
inc channelIndex
of 1:
let cy = state.components[0].channel
for y in 0 ..< state.imageHeight:
var channelIndex = cy.dataIndex(0, y)
var channelIndex = state.components[0].dataIndex(0, y)
for x in 0 ..< state.imageWidth:
result.unsafe[x, y] = grayScaleToRgbx(cy.data[channelIndex])
result.unsafe[x, y] =
grayScaleToRgbx(state.components[0].channel[channelIndex]) # cy
inc channelIndex
else:

View file

@ -616,14 +616,5 @@ proc encodePng*(image: Image): string {.raises: [PixieError].} =
copy.toStraightAlpha()
encodePng(image.width, image.height, 4, copy[0].addr, copy.len * 4)
proc encodePng*(mask: Mask): string {.raises: [PixieError].} =
## Encodes the mask data into the PNG file format.
if mask.data.len == 0:
raise newException(
PixieError,
"Mask has no data (are height and width 0?)"
)
encodePng(mask.width, mask.height, 1, mask.data[0].addr, mask.data.len)
when defined(release):
{.pop.}

View file

@ -1,6 +1,6 @@
import bumpy, chroma, common, os, pixie/fontformats/opentype,
pixie/fontformats/svgfont, pixie/images, pixie/masks, pixie/paints,
pixie/paths, strutils, unicode, vmath
pixie/fontformats/svgfont, pixie/images, pixie/paints, pixie/paths,
strutils, unicode, vmath
const
autoLineHeight*: float32 = -1 ## Use default line height for the font size
@ -561,7 +561,7 @@ proc computePaths(arrangement: Arrangement): seq[Path] =
result.add(spanPath)
proc textUber(
target: Image | Mask,
target: Image,
arrangement: Arrangement,
transform = mat3(),
strokeWidth: float32 = 1.0,
@ -575,22 +575,11 @@ proc textUber(
for spanIndex in 0 ..< arrangement.spans.len:
let path = spanPaths[spanIndex]
when stroke:
when type(target) is Image:
let font = arrangement.fonts[spanIndex]
for paint in font.paints:
target.strokePath(
path,
paint,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else: # target is Mask
let font = arrangement.fonts[spanIndex]
for paint in font.paints:
target.strokePath(
path,
paint,
transform,
strokeWidth,
lineCap,
@ -599,12 +588,9 @@ proc textUber(
dashes
)
else:
when type(target) is Image:
let font = arrangement.fonts[spanIndex]
for paint in font.paints:
target.fillPath(path, paint, transform)
else: # target is Mask
target.fillPath(path, transform)
let font = arrangement.fonts[spanIndex]
for paint in font.paints:
target.fillPath(path, paint, transform)
proc computeBounds*(
arrangement: Arrangement,
@ -617,7 +603,7 @@ proc computeBounds*(
fullPath.computeBounds()
proc fillText*(
target: Image | Mask,
target: Image,
arrangement: Arrangement,
transform = mat3()
) {.inline, raises: [PixieError].} =
@ -629,7 +615,7 @@ proc fillText*(
)
proc fillText*(
target: Image | Mask,
target: Image,
font: Font,
text: string,
transform = mat3(),
@ -645,7 +631,7 @@ proc fillText*(
fillText(target, font.typeset(text, bounds, hAlign, vAlign), transform)
proc strokeText*(
target: Image | Mask,
target: Image,
arrangement: Arrangement,
transform = mat3(),
strokeWidth: float32 = 1.0,
@ -668,7 +654,7 @@ proc strokeText*(
)
proc strokeText*(
target: Image | Mask,
target: Image,
font: Font,
text: string,
transform = mat3(),

View file

@ -1,4 +1,4 @@
import blends, bumpy, chroma, common, internal, masks, simd, vmath
import blends, bumpy, chroma, common, internal, simd, vmath
export Image, newImage
@ -9,18 +9,6 @@ type UnsafeImage = distinct Image
when defined(release):
{.push checks: off.}
proc newImage*(mask: Mask): Image {.hasSimd, raises: [PixieError].} =
result = newImage(mask.width, mask.height)
for i in 0 ..< mask.data.len:
let v = mask.data[i]
result.data[i] = rgbx(v, v, v, v)
proc newMask*(image: Image): Mask {.hasSimd, raises: [PixieError].} =
## Returns a new mask using the alpha values of the image.
result = newMask(image.width, image.height)
for i in 0 ..< image.data.len:
result.data[i] = image.data[i].a
proc copy*(image: Image): Image {.raises: [].} =
## Copies the image data into a new image.
result = Image()
@ -517,7 +505,7 @@ proc blitRect(
)
proc drawCorrect(
a, b: Image | Mask, transform = mat3(), blendMode = NormalBlend, tiled = false
a, b: Image, transform = mat3(), blendMode = NormalBlend, tiled = false
) {.raises: [PixieError].} =
## Draws one image onto another using matrix with color blending.
@ -555,44 +543,24 @@ proc drawCorrect(
transform[2, 1].fractional == 0.0
)
when type(a) is Image and type(b) is Image:
if not hasRotationOrScaling and not smooth and not tiled:
blitRect(a, b, ivec2(transform[2, 0].int32, transform[2, 1].int32), blendMode)
return
when type(a) is Image:
let blender = blendMode.blender()
else: # a is a Mask
let masker = blendMode.masker()
if not hasRotationOrScaling and not smooth and not tiled:
blitRect(a, b, ivec2(transform[2, 0].int32, transform[2, 1].int32), blendMode)
return
let blender = blendMode.blender()
for y in 0 ..< a.height:
for x in 0 ..< a.width:
let
samplePos = inverseTransform * vec2(x.float32 + h, y.float32 + h)
xFloat = samplePos.x - h
yFloat = samplePos.y - h
when type(a) is Image:
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let
sample = b.getRgbaSmooth(xFloat, yFloat, tiled)
blended = blender(backdrop, sample)
else: # b is a Mask
let
sample = b.getValueSmooth(xFloat, yFloat)
blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.unsafe[x, y] = blended
else: # a is a Mask
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let sample = b.getRgbaSmooth(xFloat, yFloat, tiled).a
else: # b is a Mask
let sample = b.getValueSmooth(xFloat, yFloat)
a.setValueUnsafe(x, y, masker(backdrop, sample))
backdrop = a.unsafe[x, y]
sample = b.getRgbaSmooth(xFloat, yFloat, tiled)
blended = blender(backdrop, sample)
a.unsafe[x, y] = blended
proc drawUber(
a, b: Image | Mask, transform = mat3(), blendMode: BlendMode
a, b: Image, transform = mat3(), blendMode: BlendMode
) {.raises: [PixieError].} =
let
corners = [
@ -650,10 +618,7 @@ proc drawUber(
yMin = yMin.clamp(0, a.height)
yMax = yMax.clamp(0, a.height)
when type(a) is Image:
let blender = blendMode.blender()
else: # a is a Mask
let maskBlender = blendMode.maskBlender()
let blender = blendMode.blender()
if blendMode == MaskBlend:
if yMin > 0:
@ -697,27 +662,12 @@ proc drawUber(
if smooth:
var srcPos = p + dx * xStart.float32 + dy * y.float32
srcPos = vec2(srcPos.x - h, srcPos.y - h)
for x in xStart ..< xStop:
when type(a) is Image:
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let
sample = b.getRgbaSmooth(srcPos.x, srcPos.y)
blended = blender(backdrop, sample)
else: # b is a Mask
let
sample = b.getValueSmooth(srcPos.x, srcPos.y)
blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.unsafe[x, y] = blended
else: # a is a Mask
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let sample = b.getRgbaSmooth(srcPos.x, srcPos.y).a
else: # b is a Mask
let sample = b.getValueSmooth(srcPos.x, srcPos.y)
a.unsafe[x, y] = maskBlender(backdrop, sample)
let
backdrop = a.unsafe[x, y]
sample = b.getRgbaSmooth(srcPos.x, srcPos.y)
blended = blender(backdrop, sample)
a.unsafe[x, y] = blended
srcPos += dx
else:
@ -728,158 +678,63 @@ proc drawUber(
sy = srcPos.y.int
var sx = srcPos.x.int
when type(a) is Image and type(b) is Image:
if blendMode in {NormalBlend, OverwriteBlend} and
isOpaque(b.data, b.dataIndex(sx, sy), xStop - xStart):
copyMem(
a.data[a.dataIndex(x, y)].addr,
b.data[b.dataIndex(sx, sy)].addr,
(xStop - xStart) * 4
)
continue
if blendMode in {NormalBlend, OverwriteBlend} and
isOpaque(b.data, b.dataIndex(sx, sy), xStop - xStart):
copyMem(
a.data[a.dataIndex(x, y)].addr,
b.data[b.dataIndex(sx, sy)].addr,
(xStop - xStart) * 4
)
continue
when defined(amd64) and allowSimd:
case blendMode:
of OverwriteBlend:
for _ in 0 ..< (xStop - xStart) div 16:
when type(a) is Image:
when type(b) is Image:
for q in [0, 4, 8, 12]:
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, sourceVec)
else: # b is a Mask
var values = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
for q in [0, 4, 8, 12]:
let sourceVec = unpackAlphaValues(values)
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, sourceVec)
# Shuffle 32 bits off for the next iteration
values = mm_srli_si128(values, 4)
else: # a is a Mask
when type(b) is Image:
var
i = mm_loadu_si128(b.data[b.dataIndex(sx + 0, sy)].addr)
j = mm_loadu_si128(b.data[b.dataIndex(sx + 4, sy)].addr)
k = mm_loadu_si128(b.data[b.dataIndex(sx + 8, sy)].addr)
l = mm_loadu_si128(b.data[b.dataIndex(sx + 12, sy)].addr)
let sourceVec = pack4xAlphaValues(i, j, k, l)
else: # b is a Mask
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
mm_storeu_si128(a.data[a.dataIndex(x, y)].addr, sourceVec)
for q in [0, 4, 8, 12]:
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, sourceVec)
x += 16
sx += 16
of NormalBlend:
let vec255 = mm_set1_epi32(cast[int32](uint32.high))
for _ in 0 ..< (xStop - xStart) div 16:
when type(a) is Image:
when type(b) is Image:
for q in [0, 4, 8, 12]:
for q in [0, 4, 8, 12]:
let
sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) != 0xffff:
let eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if (mm_movemask_epi8(eq255) and 0x8888) == 0x8888:
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, sourceVec)
else:
let
sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) != 0xffff:
let eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if (mm_movemask_epi8(eq255) and 0x8888) == 0x8888:
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, sourceVec)
else:
let
backdropIdx = a.dataIndex(x + q, y)
backdropVec = mm_loadu_si128(a.data[backdropIdx].addr)
mm_storeu_si128(
a.data[backdropIdx].addr,
blendNormalSimd(backdropVec, sourceVec)
)
else: # b is a Mask
var values = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
for q in [0, 4, 8, 12]:
let
sourceVec = unpackAlphaValues(values)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) != 0xffff:
let eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if (mm_movemask_epi8(eq255) and 0x8888) == 0x8888:
discard
else:
let
backdropIdx = a.dataIndex(x + q, y)
backdropVec = mm_loadu_si128(a.data[backdropIdx].addr)
mm_storeu_si128(
a.data[backdropIdx].addr,
blendNormalSimd(backdropVec, sourceVec)
)
# Shuffle 32 bits off for the next iteration
values = mm_srli_si128(values, 4)
else: # a is a Mask
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x, y)].addr)
when type(b) is Image:
var
i = mm_loadu_si128(b.data[b.dataIndex(sx + 0, sy)].addr)
j = mm_loadu_si128(b.data[b.dataIndex(sx + 4, sy)].addr)
k = mm_loadu_si128(b.data[b.dataIndex(sx + 8, sy)].addr)
l = mm_loadu_si128(b.data[b.dataIndex(sx + 12, sy)].addr)
let sourceVec = pack4xAlphaValues(i, j, k, l)
else: # b is a Mask
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x, y)].addr,
maskBlendNormalSimd(backdropVec, sourceVec)
)
backdropIdx = a.dataIndex(x + q, y)
backdropVec = mm_loadu_si128(a.data[backdropIdx].addr)
mm_storeu_si128(
a.data[backdropIdx].addr,
blendNormalSimd(backdropVec, sourceVec)
)
x += 16
sx += 16
of MaskBlend:
let vec255 = mm_set1_epi32(cast[int32](uint32.high))
for _ in 0 ..< (xStop - xStart) div 16:
when type(a) is Image:
when type(b) is Image:
for q in [0, 4, 8, 12]:
let
sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) == 0xffff:
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
mm_setzero_si128()
)
elif mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) != 0xffff:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
blendMaskSimd(backdropVec, sourceVec)
)
else: # b is a Mask
var values = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
for q in [0, 4, 8, 12]:
let
sourceVec = unpackAlphaValues(values)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if mm_movemask_epi8(eqZer0) == 0xffff:
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
mm_setzero_si128()
)
elif (mm_movemask_epi8(eq255) and 0x8888) != 0x8888:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
blendMaskSimd(backdropVec, sourceVec)
)
# Shuffle 32 bits off for the next iteration
values = mm_srli_si128(values, 4)
else: # a is a Mask
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x, y)].addr)
when type(b) is Image:
var
i = mm_loadu_si128(b.data[b.dataIndex(sx + 0, sy)].addr)
j = mm_loadu_si128(b.data[b.dataIndex(sx + 4, sy)].addr)
k = mm_loadu_si128(b.data[b.dataIndex(sx + 8, sy)].addr)
l = mm_loadu_si128(b.data[b.dataIndex(sx + 12, sy)].addr)
let sourceVec = pack4xAlphaValues(i, j, k, l)
else: # b is a Mask
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x, y)].addr,
maskBlendMaskSimd(backdropVec, sourceVec)
)
for q in [0, 4, 8, 12]:
let
sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) == 0xffff:
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
mm_setzero_si128()
)
elif mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) != 0xffff:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
blendMaskSimd(backdropVec, sourceVec)
)
x += 16
sx += 16
else:
@ -887,49 +742,14 @@ proc drawUber(
if blendMode.hasSimdBlender():
let blenderSimd = blendMode.blenderSimd()
for _ in 0 ..< (xStop - xStart) div 16:
when type(b) is Image:
for q in [0, 4, 8, 12]:
let
backdrop = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
source = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
blenderSimd(backdrop, source)
)
else: # b is a Mask
var values = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
for q in [0, 4, 8, 12]:
let
backdrop = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
source = unpackAlphaValues(values)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
blenderSimd(backdrop, source)
)
# Shuffle 32 bits off for the next iteration
values = mm_srli_si128(values, 4)
x += 16
sx += 16
else: # is a Mask
if blendMode.hasSimdMaskBlender():
let maskerSimd = blendMode.maskBlenderSimd()
for _ in 0 ..< (xStop - xStart) div 16:
let backdrop = mm_loadu_si128(a.data[a.dataIndex(x, y)].addr)
when type(b) is Image:
# Need to read 16 colors and pack their alpha values
for q in [0, 4, 8, 12]:
let
i = mm_loadu_si128(b.data[b.dataIndex(sx + 0, sy)].addr)
j = mm_loadu_si128(b.data[b.dataIndex(sx + 4, sy)].addr)
k = mm_loadu_si128(b.data[b.dataIndex(sx + 8, sy)].addr)
l = mm_loadu_si128(b.data[b.dataIndex(sx + 12, sy)].addr)
source = pack4xAlphaValues(i, j, k, l)
else: # b is a Mask
let source = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x, y)].addr,
maskerSimd(backdrop, source)
)
backdrop = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
source = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
blenderSimd(backdrop, source)
)
x += 16
sx += 16
@ -942,93 +762,43 @@ proc drawUber(
case blendMode:
of OverwriteBlend:
for x in x ..< xStop:
let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
when type(a) is Image:
when type(b) is Image:
let source = b.unsafe[samplePos.x, samplePos.y]
else: # b is a Mask
let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y])
if source.a > 0:
a.unsafe[x, y] = source
else: # a is a Mask
when type(b) is Image:
let source = b.unsafe[samplePos.x, samplePos.y].a
else: # b is a Mask
let source = b.unsafe[samplePos.x, samplePos.y]
if source > 0:
a.unsafe[x, y] = source
let
samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
source = b.unsafe[samplePos.x, samplePos.y]
if source.a > 0:
a.unsafe[x, y] = source
srcPos += dx
of NormalBlend:
for x in x ..< xStop:
let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
when type(a) is Image:
when type(b) is Image:
let source = b.unsafe[samplePos.x, samplePos.y]
else: # b is a Mask
let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y])
if source.a > 0:
if source.a == 255:
a.unsafe[x, y] = source
else:
let backdrop = a.unsafe[x, y]
a.unsafe[x, y] = blendNormal(backdrop, source)
else: # a is a Mask
when type(b) is Image:
let source = b.unsafe[samplePos.x, samplePos.y].a
else: # b is a Mask
let source = b.unsafe[samplePos.x, samplePos.y]
if source > 0:
if source == 255:
a.unsafe[x, y] = source
else:
let backdrop = a.unsafe[x, y]
a.unsafe[x, y] = blendAlpha(backdrop, source)
let
samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
source = b.unsafe[samplePos.x, samplePos.y]
if source.a > 0:
if source.a == 255:
a.unsafe[x, y] = source
else:
let backdrop = a.unsafe[x, y]
a.unsafe[x, y] = blendNormal(backdrop, source)
srcPos += dx
of MaskBlend:
for x in x ..< xStop:
let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
when type(a) is Image:
when type(b) is Image:
let source = b.unsafe[samplePos.x, samplePos.y]
else: # b is a Mask
let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y])
if source.a == 0:
a.unsafe[x, y] = rgbx(0, 0, 0, 0)
elif source.a != 255:
let backdrop = a.unsafe[x, y]
a.unsafe[x, y] = blendMask(backdrop, source)
else: # a is a Mask
when type(b) is Image:
let source = b.unsafe[samplePos.x, samplePos.y].a
else: # b is a Mask
let source = b.unsafe[samplePos.x, samplePos.y]
if source == 0:
a.unsafe[x, y] = 0
elif source != 255:
let backdrop = a.unsafe[x, y]
a.unsafe[x, y] = maskBlendMask(backdrop, source)
let
samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
source = b.unsafe[samplePos.x, samplePos.y]
if source.a == 0:
a.unsafe[x, y] = rgbx(0, 0, 0, 0)
elif source.a != 255:
let backdrop = a.unsafe[x, y]
a.unsafe[x, y] = blendMask(backdrop, source)
srcPos += dx
else:
for x in x ..< xStop:
let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
when type(a) is Image:
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let
sample = b.unsafe[samplePos.x, samplePos.y]
blended = blender(backdrop, sample)
else: # b is a Mask
let
sample = b.unsafe[samplePos.x, samplePos.y]
blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.unsafe[x, y] = blended
else: # a is a Mask
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let sample = b.unsafe[samplePos.x, samplePos.y].a
else: # b is a Mask
let sample = b.unsafe[samplePos.x, samplePos.y]
a.unsafe[x, y] = maskBlender(backdrop, sample)
let
samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
backdrop = a.unsafe[x, y]
sample = b.unsafe[samplePos.x, samplePos.y]
blended = blender(backdrop, sample)
a.unsafe[x, y] = blended
srcPos += dx
if blendMode == MaskBlend:
@ -1045,24 +815,6 @@ proc draw*(
## Draws one image onto another using matrix with color blending.
a.drawUber(b, transform, blendMode)
proc draw*(
a, b: Mask, transform = mat3(), blendMode = MaskBlend
) {.inline, raises: [PixieError].} =
## Draws a mask onto a mask using a matrix with color blending.
a.drawUber(b, transform, blendMode)
proc draw*(
image: Image, mask: Mask, transform = mat3(), blendMode = MaskBlend
) {.inline, raises: [PixieError].} =
## Draws a mask onto an image using a matrix with color blending.
image.drawUber(mask, transform, blendMode)
proc draw*(
mask: Mask, image: Image, transform = mat3(), blendMode = MaskBlend
) {.inline, raises: [PixieError].} =
## Draws a image onto a mask using a matrix with color blending.
mask.drawUber(image, transform, blendMode)
proc drawTiled*(
dst, src: Image, mat: Mat3, blendMode = NormalBlend
) {.raises: [PixieError].} =
@ -1083,40 +835,83 @@ proc resize*(srcImage: Image, width, height: int): Image {.raises: [PixieError].
OverwriteBlend
)
proc resize*(srcMask: Mask, width, height: int): Mask {.raises: [PixieError].} =
## Resize a mask to a given height and width.
if width == srcMask.width and height == srcMask.height:
result = srcMask.copy()
else:
result = newMask(width, height)
result.draw(
srcMask,
scale(vec2(
width.float32 / srcMask.width.float32,
height.float32 / srcMask.height.float32
)),
OverwriteBlend
)
proc spread(image: Image, spread: float32) {.raises: [PixieError].} =
## Grows the mask by spread.
let spread = round(spread).int
if spread == 0:
return
if spread > 0:
# Spread in the X direction. Store with dimensions swapped for reading later.
let spreadX = newImage(image.height, image.width)
for y in 0 ..< image.height:
for x in 0 ..< image.width:
var maxValue: uint8
for xx in max(x - spread, 0) .. min(x + spread, image.width - 1):
let value = image.unsafe[xx, y].a
if value > maxValue:
maxValue = value
if maxValue == 255:
break
spreadX.unsafe[y, x] = rgbx(0, 0, 0, maxValue)
# Spread in the Y direction and modify mask.
for y in 0 ..< image.height:
for x in 0 ..< image.width:
var maxValue: uint8
for yy in max(y - spread, 0) .. min(y + spread, image.height - 1):
let value = spreadX.unsafe[yy, x].a
if value > maxValue:
maxValue = value
if maxValue == 255:
break
image.unsafe[x, y] = rgbx(0, 0, 0, maxValue)
elif spread < 0:
let spread = -spread
# Spread in the X direction. Store with dimensions swapped for reading later.
let spreadX = newImage(image.height, image.width)
for y in 0 ..< image.height:
for x in 0 ..< image.width:
var minValue: uint8 = 255
for xx in max(x - spread, 0) .. min(x + spread, image.width - 1):
let value = image.unsafe[xx, y].a
if value < minValue:
minValue = value
if minValue == 0:
break
spreadX.unsafe[y, x] = rgbx(0, 0, 0, minValue)
# Spread in the Y direction and modify mask.
for y in 0 ..< image.height:
for x in 0 ..< image.width:
var minValue: uint8 = 255
for yy in max(y - spread, 0) .. min(y + spread, image.height - 1):
let value = spreadX.unsafe[yy, x].a
if value < minValue:
minValue = value
if minValue == 0:
break
image.unsafe[x, y] = rgbx(0, 0, 0, minValue)
proc shadow*(
image: Image, offset: Vec2, spread, blur: float32, color: SomeColor
): Image {.raises: [PixieError].} =
## Create a shadow of the image with the offset, spread and blur.
let mask = image.newMask()
var shifted: Mask
var mask: Image
if offset == vec2(0, 0):
shifted = mask
mask = image.copy()
else:
shifted = newMask(mask.width, mask.height)
shifted.draw(mask, translate(offset), OverwriteBlend)
mask = newImage(image.width, image.height)
mask.draw(image, translate(offset), OverwriteBlend)
shifted.spread(spread)
shifted.blur(blur)
mask.spread(spread)
mask.blur(blur)
result = newImage(shifted.width, shifted.height)
result = newImage(mask.width, mask.height)
result.fill(color)
result.draw(shifted)
result.draw(mask, blendMode = MaskBlend)
proc superImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} =
## Either cuts a sub image or returns a super image with padded transparency.

View file

@ -59,13 +59,6 @@ proc intersectsInside*(a, b: Segment, at: var Vec2): bool {.inline.} =
at = a.at + (t * s1)
return true
proc fillUnsafe*(
data: var seq[uint8], value: uint8, start, len: int
) {.inline, raises: [].} =
## Fills the mask data with the value starting at index start and
## continuing for len indices.
nimSetMem(data[start].addr, value.cint, len)
proc fillUnsafe*(
data: var seq[ColorRGBX], color: SomeColor, start, len: int
) {.hasSimd, raises: [].} =
@ -77,7 +70,7 @@ proc fillUnsafe*(
nimSetMem(data[start].addr, rgbx.r.cint, len * 4)
else:
for i in start ..< start + len:
data[i] = rgbx
data[i] = rgbx
const straightAlphaTable = block:
var table: array[256, array[256, uint8]]

View file

@ -1,319 +0,0 @@
import common, internal, simd, vmath
export Mask, newMask
type UnsafeMask = distinct Mask
when defined(release):
{.push checks: off.}
proc copy*(mask: Mask): Mask {.raises: [].} =
## Copies the image data into a new image.
result = Mask()
result.width = mask.width
result.height = mask.height
result.data = mask.data
proc `$`*(mask: Mask): string {.raises: [].} =
## Prints the mask size.
"<Mask " & $mask.width & "x" & $mask.height & ">"
proc inside*(mask: Mask, x, y: int): bool {.inline, raises: [].} =
## Returns true if (x, y) is inside the mask.
x >= 0 and x < mask.width and y >= 0 and y < mask.height
proc dataIndex*(mask: Mask, x, y: int): int {.inline, raises: [].} =
mask.width * y + x
template unsafe*(src: Mask): UnsafeMask =
cast[UnsafeMask](src)
template `[]`*(view: UnsafeMask, x, y: int): uint8 =
## Gets a value from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory reads.
cast[Mask](view).data[cast[Mask](view).dataIndex(x, y)]
template `[]=`*(view: UnsafeMask, x, y: int, color: uint8) =
## Sets a value from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory writes.
cast[Mask](view).data[cast[Mask](view).dataIndex(x, y)] = color
proc `[]`*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
## Gets a value at (x, y) or returns transparent black if outside of bounds.
if mask.inside(x, y):
return mask.unsafe[x, y]
proc `[]=`*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} =
## Sets a value at (x, y) or does nothing if outside of bounds.
if mask.inside(x, y):
mask.unsafe[x, y] = value
proc getValue*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
## Gets a value at (x, y) or returns transparent black if outside of bounds.
mask[x, y]
proc setValue*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} =
## Sets a value at (x, y) or does nothing if outside of bounds.
mask[x, y] = value
proc fill*(mask: Mask, value: uint8) {.inline, raises: [].} =
## Fills the mask with the value.
fillUnsafe(mask.data, value, 0, mask.data.len)
proc minifyBy2*(mask: Mask, power = 1): Mask {.raises: [PixieError].} =
## Scales the mask down by an integer scale.
if power < 0:
raise newException(PixieError, "Cannot minifyBy2 with negative power")
if power == 0:
return mask.copy()
var src = mask
for i in 1 .. power:
result = newMask(src.width div 2, src.height div 2)
for y in 0 ..< result.height:
var x: int
when defined(amd64) and allowSimd:
let oddMask = mm_set1_epi16(cast[int16](0xff00))
while x <= result.width - 16:
let
top = mm_loadu_si128(src.data[src.dataIndex(x * 2, y * 2 + 0)].addr)
btm = mm_loadu_si128(src.data[src.dataIndex(x * 2, y * 2 + 1)].addr)
topShifted = mm_srli_si128(top, 1)
btmShifted = mm_srli_si128(btm, 1)
topEven = mm_andnot_si128(oddMask, top)
topOdd = mm_srli_epi16(top, 8)
btmEven = mm_andnot_si128(oddMask, btm)
btmOdd = mm_srli_epi16(btm, 8)
topShiftedEven = mm_andnot_si128(oddMask, topShifted)
topShiftedOdd = mm_srli_epi16(topShifted, 8)
btmShiftedEven = mm_andnot_si128(oddMask, btmShifted)
btmShiftedOdd = mm_srli_epi16(btmShifted, 8)
topAddedEven = mm_add_epi16(topEven, topShiftedEven)
btmAddedEven = mm_add_epi16(btmEven, btmShiftedEven)
topAddedOdd = mm_add_epi16(topOdd, topShiftedOdd)
btmAddedOdd = mm_add_epi16(btmOdd, btmShiftedOdd)
addedEven = mm_add_epi16(topAddedEven, btmAddedEven)
addedOdd = mm_add_epi16(topAddedOdd, btmAddedOdd)
addedEvenDiv4 = mm_srli_epi16(addedEven, 2)
addedOddDiv4 = mm_srli_epi16(addedOdd, 2)
merged = mm_or_si128(addedEvenDiv4, mm_slli_epi16(addedOddDiv4, 8))
# Merged has the correct values in the even indices
# Mask out the odd values for packing
masked = mm_andnot_si128(oddMask, merged)
mm_storeu_si128(
result.data[result.dataIndex(x, y)].addr,
mm_packus_epi16(masked, mm_setzero_si128())
)
x += 8
for x in x ..< result.width:
let value =
src.unsafe[x * 2 + 0, y * 2 + 0].uint32 +
src.unsafe[x * 2 + 1, y * 2 + 0] +
src.unsafe[x * 2 + 1, y * 2 + 1] +
src.unsafe[x * 2 + 0, y * 2 + 1]
result.unsafe[x, y] = (value div 4).uint8
# Set src as this result for if we do another power
src = result
proc magnifyBy2*(mask: Mask, power = 1): Mask {.raises: [PixieError].} =
## Scales mask up by 2 ^ power.
if power < 0:
raise newException(PixieError, "Cannot magnifyBy2 with negative power")
let scale = 2 ^ power
result = newMask(mask.width * scale, mask.height * scale)
for y in 0 ..< mask.height:
# Write one row of values duplicated by scale
var x: int
when defined(amd64) and allowSimd:
if scale == 2:
while x <= mask.width - 16:
let values = mm_loadu_si128(mask.unsafe[x, y].addr)
mm_storeu_si128(
result.data[result.dataIndex(x * scale + 0, y * scale)].addr,
mm_unpacklo_epi8(values, values)
)
mm_storeu_si128(
result.data[result.dataIndex(x * scale + 16, y * scale)].addr,
mm_unpackhi_epi8(values, values)
)
x += 16
for x in x ..< mask.width:
let
value = mask.unsafe[x, y]
resultIdx = result.dataIndex(x * scale, y * scale)
for i in 0 ..< scale:
result.data[resultIdx + i] = value
# Copy that row of values into (scale - 1) more rows
let rowStart = result.dataIndex(0, y * scale)
for i in 1 ..< scale:
copyMem(
result.data[rowStart + result.width * i].addr,
result.data[rowStart].addr,
result.width
)
proc applyOpacity*(mask: Mask, opacity: float32) {.hasSimd, raises: [].} =
## Multiplies alpha of the image by opacity.
let opacity = round(255 * opacity).uint16
if opacity == 255:
return
if opacity == 0:
mask.fill(0)
return
for i in 0 ..< mask.data.len:
mask.data[i] = ((mask.data[i] * opacity) div 255).uint8
proc getValueSmooth*(mask: Mask, x, y: float32): uint8 {.raises: [].} =
## Gets a interpolated value with float point coordinates.
let
x0 = x.int
y0 = y.int
x1 = x0 + 1
y1 = y0 + 1
xFractional = x.fractional
yFractional = y.fractional
x0y0 = mask[x0, y0]
x1y0 = mask[x1, y0]
x0y1 = mask[x0, y1]
x1y1 = mask[x1, y1]
var topMix = x0y0
if xFractional > 0 and x0y0 != x1y0:
topMix = mix(x0y0, x1y0, xFractional)
var bottomMix = x0y1
if xFractional > 0 and x0y1 != x1y1:
bottomMix = mix(x0y1, x1y1, xFractional)
if yFractional != 0 and topMix != bottomMix:
mix(topMix, bottomMix, yFractional)
else:
topMix
proc invert*(mask: Mask) {.hasSimd, raises: [].} =
## Inverts all of the values - creates a negative of the mask.
for i in 0 ..< mask.data.len:
mask.data[i] = 255 - mask.data[i]
proc spread*(mask: Mask, spread: float32) {.raises: [PixieError].} =
## Grows the mask by spread.
let spread = round(spread).int
if spread == 0:
return
if spread > 0:
# Spread in the X direction. Store with dimensions swapped for reading later.
let spreadX = newMask(mask.height, mask.width)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var maxValue: uint8
for xx in max(x - spread, 0) .. min(x + spread, mask.width - 1):
let value = mask.unsafe[xx, y]
if value > maxValue:
maxValue = value
if maxValue == 255:
break
spreadX.unsafe[y, x] = maxValue
# Spread in the Y direction and modify mask.
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var maxValue: uint8
for yy in max(y - spread, 0) .. min(y + spread, mask.height - 1):
let value = spreadX.unsafe[yy, x]
if value > maxValue:
maxValue = value
if maxValue == 255:
break
mask.unsafe[x, y] = maxValue
elif spread < 0:
# Spread in the X direction. Store with dimensions swapped for reading later.
let spread = -spread
let spreadX = newMask(mask.height, mask.width)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var maxValue: uint8 = 255
for xx in max(x - spread, 0) .. min(x + spread, mask.width - 1):
let value = mask.unsafe[xx, y]
if value < maxValue:
maxValue = value
if maxValue == 0:
break
spreadX.unsafe[y, x] = maxValue
# Spread in the Y direction and modify mask.
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var maxValue: uint8 = 255
for yy in max(y - spread, 0) .. min(y + spread, mask.height - 1):
let value = spreadX.unsafe[yy, x]
if value < maxValue:
maxValue = value
if maxValue == 0:
break
mask.unsafe[x, y] = maxValue
proc ceil*(mask: Mask) {.hasSimd, raises: [].} =
## A value of 0 stays 0. Anything else turns into 255.
for i in 0 ..< mask.data.len:
if mask.data[i] != 0:
mask.data[i] = 255
proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) {.raises: [PixieError].} =
## Applies Gaussian blur to the image given a radius.
let radius = round(radius).int
if radius == 0:
return
if radius < 0:
raise newException(PixieError, "Cannot apply negative blur")
let kernel = gaussianKernel(radius)
# Blur in the X direction. Store with dimensions swapped for reading later.
let blurX = newMask(mask.height, mask.width)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var value: uint32
for xx in x - radius ..< min(x + radius, 0):
value += outOfBounds * kernel[xx - x + radius].uint32
for xx in max(x - radius, 0) .. min(x + radius, mask.width - 1):
value += mask.unsafe[xx, y] * kernel[xx - x + radius].uint32
for xx in max(x - radius, mask.width) .. x + radius:
value += outOfBounds * kernel[xx - x + radius].uint32
blurX.unsafe[y, x] = (value div 256 div 255).uint8
# Blur in the Y direction and modify image.
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var value: uint32
for yy in y - radius ..< min(y + radius, 0):
value += outOfBounds * kernel[yy - y + radius].uint32
for yy in max(y - radius, 0) .. min(y + radius, mask.height - 1):
value += blurX.unsafe[yy, x] * kernel[yy - y + radius].uint32
for yy in max(y - radius, mask.height) .. y + radius:
value += outOfBounds * kernel[yy - y + radius].uint32
mask.unsafe[x, y] = (value div 256 div 255).uint8
when defined(release):
{.pop.}

View file

@ -1,5 +1,5 @@
import blends, bumpy, chroma, common, images, internal, masks, paints, simd,
std/fenv, std/strutils, vmath
import blends, bumpy, chroma, common, images, internal, paints, simd, std/fenv,
std/strutils, vmath
type
WindingRule* = enum
@ -1132,7 +1132,7 @@ proc initPartitionEntry(segment: Segment, winding: int16): PartitionEntry =
result.m = (segment.at.y - segment.to.y) / d
result.b = segment.at.y - result.m * segment.at.x
proc solveX(entry: PartitionEntry, y: float32): float32 {.inline.} =
proc solveX(entry: PartitionEntry, y: float32): float32 {.inline.} =
if entry.m == 0:
entry.b
else:
@ -1420,17 +1420,14 @@ proc computeCoverage(
for j in i ..< fillStart + fillLen:
coverages[j - startX] += sampleCoverage
proc clearUnsafe(target: Image | Mask, startX, startY, toX, toY: int) =
proc clearUnsafe(image: Image, startX, startY, toX, toY: int) =
## Clears data from [start, to).
if startX == target.width or startY == target.height:
if startX == image.width or startY == image.height:
return
let
start = target.dataIndex(startX, startY)
len = target.dataIndex(toX, toY) - start
when type(target) is Image:
fillUnsafe(target.data, rgbx(0, 0, 0, 0), start, len)
else: # target is Mask
fillUnsafe(target.data, 0, start, len)
start = image.dataIndex(startX, startY)
len = image.dataIndex(toX, toY) - start
fillUnsafe(image.data, rgbx(0, 0, 0, 0), start, len)
proc fillCoverage(
image: Image,
@ -1618,98 +1615,6 @@ proc fillCoverage(
image.data[dataIndex] = blender(backdrop, source(rgbx, coverage))
inc dataIndex
proc fillCoverage(
mask: Mask,
startX, y: int,
coverages: seq[uint8],
blendMode: BlendMode
) =
var
x = startX
dataIndex = mask.dataIndex(x, y)
template simdBlob(blendProc: untyped) =
when allowSimd:
when defined(amd64):
for _ in 0 ..< coverages.len div 16:
let
coveragesVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
eqZero = mm_cmpeq_epi8(coveragesVec, mm_setzero_si128())
allZeroes = mm_movemask_epi8(eqZero) == 0xffff
if not allZeroes:
let backdrop = mm_loadu_si128(mask.data[dataIndex].addr)
mm_storeu_si128(
mask.data[dataIndex].addr,
blendProc(backdrop, coveragesVec)
)
x += 16
dataIndex += 16
template blendBlob(blendProc: untyped) =
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0:
let backdrop = mask.data[dataIndex]
mask.data[dataIndex] = blendProc(backdrop, coverage)
inc dataIndex
case blendMode:
of OverwriteBlend:
copyMem(
mask.unsafe[startX, y].addr,
coverages[0].unsafeAddr,
coverages.len
)
of NormalBlend:
simdBlob(maskBlendNormalSimd)
blendBlob(maskBlendNormal)
of MaskBlend:
{.linearScanEnd.}
when allowSimd:
when defined(amd64):
for _ in 0 ..< coverages.len div 16:
let
coveragesVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
eqZero = mm_cmpeq_epi8(coveragesVec, mm_setzero_si128())
allZeroes = mm_movemask_epi8(eqZero) == 0xffff
if not allZeroes:
let backdrop = mm_loadu_si128(mask.data[dataIndex].addr)
mm_storeu_si128(
mask.data[dataIndex].addr,
maskBlendMaskSimd(backdrop, coveragesVec)
)
else:
mm_storeu_si128(mask.data[dataIndex].addr, mm_setzero_si128())
x += 16
dataIndex += 16
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0:
let backdrop = mask.data[dataIndex]
mask.data[dataIndex] = maskBlendMask(backdrop, coverage)
else:
mask.data[dataIndex] = 0
inc dataIndex
mask.clearUnsafe(0, y, startX, y)
mask.clearUnsafe(startX + coverages.len, y, mask.width, y)
of SubtractMaskBlend:
simdBlob(maskBlendSubtractSimd)
blendBlob(maskBlendSubtract)
of ExcludeMaskBlend:
simdBlob(maskBlendExcludeSimd)
blendBlob(maskBlendExclude)
else:
let maskBlender = blendMode.maskBlender()
blendBlob(maskBlender)
proc fillHits(
image: Image,
rgbx: ColorRGBX,
@ -1717,7 +1622,8 @@ proc fillHits(
hits: seq[(Fixed32, int16)],
numHits: int,
windingRule: WindingRule,
blendMode: BlendMode
blendMode: BlendMode,
maskClears = true
) =
template simdBlob(image: Image, x: var int, len: int, blendProc: untyped) =
when allowSimd:
@ -1755,7 +1661,7 @@ proc fillHits(
var filledTo = startX
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):
block: # Clear any gap between this fill and the previous fill
if maskClears: # Clear any gap between this fill and the previous fill
let gapBetween = start - filledTo
if gapBetween > 0:
fillUnsafe(
@ -1764,7 +1670,6 @@ proc fillHits(
image.dataIndex(filledTo, y),
gapBetween
)
filledTo = start + len
block: # Handle this fill
if rgbx.a != 255:
var x = start
@ -1773,9 +1678,11 @@ proc fillHits(
for _ in x ..< start + len:
let backdrop = image.data[dataIndex]
image.data[dataIndex] = blendMask(backdrop, rgbx)
filledTo = start + len
image.clearUnsafe(0, y, startX, y)
image.clearUnsafe(filledTo, y, image.width, y)
if maskClears:
image.clearUnsafe(0, y, startX, y)
image.clearUnsafe(filledTo, y, image.width, y)
of SubtractMaskBlend:
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):
@ -1805,68 +1712,6 @@ proc fillHits(
image.data[dataIndex] = blender(backdrop, rgbx)
inc dataIndex
proc fillHits(
mask: Mask,
startX, y: int,
hits: seq[(Fixed32, int16)],
numHits: int,
windingRule: WindingRule,
blendMode: BlendMode
) =
template simdBlob(mask: Mask, x: var int, len: int, blendProc: untyped) =
when allowSimd:
when defined(amd64):
var p = cast[uint](mask.data[mask.dataIndex(x, y)].addr)
let
iterations = len div 16
vec255 = mm_set1_epi8(255)
for _ in 0 ..< iterations:
let backdrop = mm_loadu_si128(cast[pointer](p))
mm_storeu_si128(cast[pointer](p), blendProc(backdrop, vec255))
p += 16
x += iterations * 16
case blendMode:
of NormalBlend, OverwriteBlend:
for (start, len) in hits.walkInteger(numHits, windingRule, y, mask.width):
fillUnsafe(mask.data, 255, mask.dataIndex(start, y), len)
of MaskBlend:
{.linearScanEnd.}
var filledTo = startX
for (start, len) in hits.walkInteger(numHits, windingRule, y, mask.width):
let gapBetween = start - filledTo
if gapBetween > 0:
fillUnsafe(mask.data, 0, mask.dataIndex(filledTo, y), gapBetween)
filledTo = start + len
mask.clearUnsafe(0, y, startX, y)
mask.clearUnsafe(filledTo, y, mask.width, y)
of SubtractMaskBlend:
for (start, len) in hits.walkInteger(numHits, windingRule, y, mask.width):
var x = start
simdBlob(mask, x, len, maskBlendSubtractSimd)
var dataIndex = mask.dataIndex(x, y)
for _ in x ..< start + len:
let backdrop = mask.data[dataIndex]
mask.data[dataIndex] = maskBlendSubtract(backdrop, 255)
inc dataIndex
of ExcludeMaskBlend:
for (start, len) in hits.walkInteger(numHits, windingRule, y, mask.width):
var x = start
simdBlob(mask, x, len, maskBlendExcludeSimd)
var dataIndex = mask.dataIndex(x, y)
for _ in x ..< start + len:
let backdrop = mask.data[dataIndex]
mask.data[dataIndex] = maskBlendExclude(backdrop, 255)
inc dataIndex
else:
failUnsupportedBlendMode(blendMode)
proc fillShapes(
image: Image,
shapes: seq[Polygon],
@ -2021,9 +1866,7 @@ proc fillShapes(
i += 2
if onlySimpleFillPairs:
numHits = 0
var i: int
var i, filledTo: int
while i < numEntryIndices:
let
left = partition.entries[entryIndices[i]]
@ -2128,13 +1971,24 @@ proc fillShapes(
let
fillBegin = leftCoverEnd.clamp(0, image.width)
fillEnd = rightCoverBegin.clamp(0, image.width)
hits[numHits] = (fixed32(fillBegin.float32), 1.int16)
hits[numHits + 1] = (fixed32(fillEnd.float32), -1.int16)
numHits += 2
hits[0] = (fixed32(fillBegin.float32), 1.int16)
hits[1] = (fixed32(fillEnd.float32), -1.int16)
image.fillHits(rgbx, 0, y, hits, 2, NonZero, blendMode, false)
if blendMode == MaskBlend:
let clearTo = min(trapLeft.at.x, trapLeft.to.x).int
image.clearUnsafe(
min(filledTo, image.width),
y,
min(clearTo, image.width),
y
)
filledTo = max(trapRight.at.x, trapRight.to.x).ceil.int
i += 2
if numHits > 0:
image.fillHits(rgbx, 0, y, hits, numHits, NonZero, blendMode)
if blendMode == MaskBlend:
image.clearUnsafe(min(filledTo, image.width), y, image.width, y)
inc y
continue
@ -2179,93 +2033,6 @@ proc fillShapes(
image.clearUnsafe(0, 0, 0, startY)
image.clearUnsafe(0, pathHeight, 0, image.height)
proc fillShapes(
mask: Mask,
shapes: seq[Polygon],
windingRule: WindingRule,
blendMode: BlendMode
) =
# Figure out the total bounds of all the shapes,
# rasterize only within the total bounds
let
segments = shapes.shapesToSegments()
bounds = computeBounds(segments).snapToPixels()
startX = max(0, bounds.x.int)
startY = max(0, bounds.y.int)
pathWidth =
if startX < mask.width:
min(bounds.w.int, mask.width - startX)
else:
0
pathHeight = min(mask.height, (bounds.y + bounds.h).int)
if pathWidth == 0:
return
if pathWidth < 0:
raise newException(PixieError, "Path int overflow detected")
var
partitions = partitionSegments(segments, startY, pathHeight)
partitionIndex: int
entryIndices = newSeq[int](partitions.maxEntryCount)
numEntryIndices: int
coverages = newSeq[uint8](pathWidth)
hits = newSeq[(Fixed32, int16)](partitions.maxEntryCount)
numHits: int
for y in startY ..< pathHeight:
if y >= partitions[partitionIndex].bottom:
inc partitionIndex
let
partition = partitions[partitionIndex].addr
partitionTop = partition.top
partitionBottom = partition.bottom
partitionHeight = partitionBottom - partitionTop
if partitionHeight == 0:
continue
let
scanTop = y.float32
scanBottom = (y + 1).float32
numEntryIndices = 0
if partition.twoNonintersectingSpanningSegments:
numEntryIndices = 2
entryIndices[0] = 0
entryIndices[1] = 1
else:
for i in 0 ..< partition.entries.len:
if partition.entries[i].segment.to.y < scanTop or
partition.entries[i].segment.at.y >= scanBottom:
continue
entryIndices[numEntryIndices] = i
inc numEntryIndices
computeCoverage(
cast[ptr UncheckedArray[uint8]](coverages[0].addr),
hits,
numHits,
mask.width,
y,
startX,
partitions,
partitionIndex,
entryIndices,
numEntryIndices,
windingRule
)
if partitions[partitionIndex].requiresAntiAliasing:
mask.fillCoverage(startX, y, coverages, blendMode)
zeroMem(coverages[0].addr, coverages.len)
else:
mask.fillHits(startX, y, hits, numHits, windingRule, blendMode)
if blendMode == MaskBlend:
mask.clearUnsafe(0, 0, 0, startY)
mask.clearUnsafe(0, pathHeight, 0, mask.height)
proc miterLimitToAngle*(limit: float32): float32 {.inline.} =
## Converts miter-limit-ratio to miter-limit-angle.
arcsin(1 / limit) * 2
@ -2445,18 +2212,6 @@ proc parseSomePath(
elif type(path) is Path:
path.commandsToShapes(closeSubpaths, pixelScale)
proc fillPath*(
mask: Mask,
path: SomePath,
transform = mat3(),
windingRule = NonZero,
blendMode = NormalBlend
) {.raises: [PixieError].} =
## Fills a path.
var shapes = parseSomePath(path, true, transform.pixelScale())
shapes.transform(transform)
mask.fillShapes(shapes, windingRule, blendMode)
proc fillPath*(
image: Image,
path: SomePath,
@ -2480,10 +2235,10 @@ proc fillPath*(
return
let
mask = newMask(image.width, image.height)
mask = newImage(image.width, image.height)
fill = newImage(image.width, image.height)
mask.fillPath(path, transform, windingRule)
mask.fillPath(path, color(1, 1, 1, 1), transform, windingRule)
# Draw the image (maybe tiled) or gradients. Do this with opaque paint and
# and then apply the paint's opacity to the mask.
@ -2505,34 +2260,9 @@ proc fillPath*(
if paint.opacity != 1:
mask.applyOpacity(paint.opacity)
fill.draw(mask)
fill.draw(mask, blendMode = MaskBlend)
image.draw(fill, blendMode = paint.blendMode)
proc strokePath*(
mask: Mask,
path: SomePath,
transform = mat3(),
strokeWidth: float32 = 1.0,
lineCap = ButtCap,
lineJoin = MiterJoin,
miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[],
blendMode = NormalBlend
) {.raises: [PixieError].} =
## Strokes a path.
let pixelScale = transform.pixelScale()
var strokeShapes = strokeShapes(
parseSomePath(path, false, pixelScale),
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes,
pixelScale
)
strokeShapes.transform(transform)
mask.fillShapes(strokeShapes, NonZero, blendMode)
proc strokePath*(
image: Image,
path: SomePath,
@ -2568,11 +2298,12 @@ proc strokePath*(
return
let
mask = newMask(image.width, image.height)
mask = newImage(image.width, image.height)
fill = newImage(image.width, image.height)
mask.strokePath(
path,
color(1, 1, 1, 1),
transform,
strokeWidth,
lineCap,
@ -2601,7 +2332,7 @@ proc strokePath*(
if paint.opacity != 1:
mask.applyOpacity(paint.opacity)
fill.draw(mask)
fill.draw(mask, blendMode = MaskBlend)
image.draw(fill, blendMode = paint.blendMode)
proc overlaps(

View file

@ -1,6 +1,6 @@
import simd/internal, system/memory
import simd/internal
export internal, memory
export internal
const allowSimd* = not defined(pixieNoSimd) and not defined(tcc)

View file

@ -1,4 +1,4 @@
import chroma, internal, nimsimd/neon, pixie/common, system/memory, vmath
import chroma, internal, nimsimd/neon, pixie/common, vmath
when defined(release):
{.push checks: off.}
@ -160,40 +160,6 @@ proc toPremultipliedAlphaNeon*(data: var seq[ColorRGBA | ColorRGBX]) {.simd.} =
c.b = ((c.b.uint32 * c.a + 127) div 255).uint8
data[i] = c
proc newImageNeon*(mask: Mask): Image {.simd.} =
result = newImage(mask.width, mask.height)
var i: int
for _ in 0 ..< mask.data.len div 16:
let alphas = vld1q_u8(mask.data[i].addr)
template doLane(lane: int) =
let packed = vgetq_lane_u32(cast[uint32x4](alphas), lane)
var unpacked = cast[uint8x16](vmovq_n_u32(packed))
unpacked = vzip1q_u8(unpacked, unpacked)
unpacked = vzip1q_u8(unpacked, unpacked)
vst1q_u8(result.data[i + lane * 4].addr, unpacked)
doLane(0)
doLane(1)
doLane(2)
doLane(3)
i += 16
for i in i ..< mask.data.len:
let v = mask.data[i]
result.data[i] = rgbx(v, v, v, v)
proc newMaskNeon*(image: Image): Mask {.simd.} =
result = newMask(image.width, image.height)
var i: int
for _ in 0 ..< image.data.len div 16:
let alphas = vld4q_u8(image.data[i].addr).val[3]
vst1q_u8(result.data[i].addr, alphas)
i += 16
for i in i ..< image.data.len:
result.data[i] = image.data[i].a
proc invertNeon*(image: Image) {.simd.} =
var
i: int
@ -232,49 +198,6 @@ proc invertNeon*(image: Image) {.simd.} =
toPremultipliedAlphaNeon(image.data)
proc invertNeon*(mask: Mask) {.simd.} =
var
i: int
p = cast[uint](mask.data[0].addr)
# Align to 16 bytes
while i < mask.data.len and (p and 15) != 0:
mask.data[i] = 255 - mask.data[i]
inc i
inc p
let
vec255 = vmovq_n_u8(255)
iterations = mask.data.len div 16
for _ in 0 ..< iterations:
let values = vld1q_u8(cast[pointer](p))
vst1q_u8(cast[pointer](p), vsubq_u8(vec255, values))
p += 16
i += 16 * iterations
for i in i ..< mask.data.len:
mask.data[i] = 255 - mask.data[i]
proc ceilNeon*(mask: Mask) {.simd.} =
var
i: int
p = cast[uint](mask.data[0].addr)
let
zeroVec = vmovq_n_u8(0)
vec255 = vmovq_n_u8(255)
iterations = mask.data.len div 16
for _ in 0 ..< iterations:
var values = vld1q_u8(cast[pointer](p))
values = vceqq_u8(values, zeroVec)
values = vbicq_u8(vec255, values)
vst1q_u8(cast[pointer](p), values)
p += 16
i += 16 * iterations
for i in i ..< mask.data.len:
if mask.data[i] != 0:
mask.data[i] = 255
proc applyOpacityNeon*(image: Image, opacity: float32) {.simd.} =
let opacity = round(255 * opacity).uint8
if opacity == 255:
@ -313,32 +236,5 @@ proc applyOpacityNeon*(image: Image, opacity: float32) {.simd.} =
rgbx.a = ((rgbx.a * opacity) div 255).uint8
image.data[i] = rgbx
proc applyOpacityNeon*(mask: Mask, opacity: float32) {.simd.} =
let opacity = round(255 * opacity).uint8
if opacity == 255:
return
if opacity == 0:
nimSetMem(mask.data[0].addr, 0.cint, mask.data.len)
var
i: int
p = cast[uint](mask.data[0].addr)
let
opacityVec = vmov_n_u8(opacity)
iterations = mask.data.len div 8
for _ in 0 ..< iterations:
let
values = vld1_u8(cast[pointer](p))
multiplied = vmull_u8(values, opacityVec)
rounded = vraddhn_u16(multiplied, vrshrq_n_u16(multiplied, 8))
vst1_u8(cast[pointer](p), rounded)
p += 8
i += 8 * iterations
for i in i ..< mask.data.len:
mask.data[i] = ((mask.data[i] * opacity) div 255).uint8
when defined(release):
{.pop.}

View file

@ -1,4 +1,4 @@
import chroma, internal, nimsimd/sse2, pixie/common, system/memory, vmath
import chroma, internal, nimsimd/sse2, pixie/common, vmath
when defined(release):
{.push checks: off.}
@ -207,43 +207,6 @@ proc toPremultipliedAlphaSse2*(data: var seq[ColorRGBA | ColorRGBX]) {.simd.} =
c.b = ((c.b.uint32 * c.a + 127) div 255).uint8
data[i] = c
proc newImageSse2*(mask: Mask): Image {.simd.} =
result = newImage(mask.width, mask.height)
var i: int
for _ in 0 ..< mask.data.len div 16:
var alphas = mm_loadu_si128(mask.data[i].addr)
for j in 0 ..< 4:
var unpacked = unpackAlphaValues(alphas)
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 8))
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16))
mm_storeu_si128(result.data[i + j * 4].addr, unpacked)
alphas = mm_srli_si128(alphas, 4)
i += 16
for i in i ..< mask.data.len:
let v = mask.data[i]
result.data[i] = rgbx(v, v, v, v)
proc newMaskSse2*(image: Image): Mask {.simd.} =
result = newMask(image.width, image.height)
var i: int
for _ in 0 ..< image.data.len div 16:
let
a = mm_loadu_si128(image.data[i + 0].addr)
b = mm_loadu_si128(image.data[i + 4].addr)
c = mm_loadu_si128(image.data[i + 8].addr)
d = mm_loadu_si128(image.data[i + 12].addr)
mm_storeu_si128(
result.data[i].addr,
pack4xAlphaValues(a, b, c, d)
)
i += 16
for i in i ..< image.data.len:
result.data[i] = image.data[i].a
proc invertSse2*(image: Image) {.simd.} =
var
i: int
@ -285,56 +248,6 @@ proc invertSse2*(image: Image) {.simd.} =
toPremultipliedAlphaSse2(image.data)
proc invertSse2*(mask: Mask) {.simd.} =
var
i: int
p = cast[uint](mask.data[0].addr)
# Align to 16 bytes
while i < mask.data.len and (p and 15) != 0:
mask.data[i] = 255 - mask.data[i]
inc i
inc p
let
vec255 = mm_set1_epi8(255)
iterations = mask.data.len div 64
for _ in 0 ..< iterations:
let
a = mm_load_si128(cast[pointer](p))
b = mm_load_si128(cast[pointer](p + 16))
c = mm_load_si128(cast[pointer](p + 32))
d = mm_load_si128(cast[pointer](p + 48))
mm_store_si128(cast[pointer](p), mm_sub_epi8(vec255, a))
mm_store_si128(cast[pointer](p + 16), mm_sub_epi8(vec255, b))
mm_store_si128(cast[pointer](p + 32), mm_sub_epi8(vec255, c))
mm_store_si128(cast[pointer](p + 48), mm_sub_epi8(vec255, d))
p += 64
i += 64 * iterations
for i in i ..< mask.data.len:
mask.data[i] = 255 - mask.data[i]
proc ceilSse2*(mask: Mask) {.simd.} =
var
i: int
p = cast[uint](mask.data[0].addr)
let
zeroVec = mm_setzero_si128()
vec255 = mm_set1_epi8(255)
iterations = mask.data.len div 16
for _ in 0 ..< iterations:
var values = mm_loadu_si128(cast[pointer](p))
values = mm_cmpeq_epi8(values, zeroVec)
values = mm_andnot_si128(values, vec255)
mm_storeu_si128(cast[pointer](p), values)
p += 16
i += 16 * iterations
for i in i ..< mask.data.len:
if mask.data[i] != 0:
mask.data[i] = 255
proc applyOpacitySse2*(image: Image, opacity: float32) {.simd.} =
let opacity = round(255 * opacity).uint16
if opacity == 255:
@ -379,45 +292,9 @@ proc applyOpacitySse2*(image: Image, opacity: float32) {.simd.} =
rgbx.a = ((rgbx.a * opacity) div 255).uint8
image.data[i] = rgbx
proc applyOpacitySse2*(mask: Mask, opacity: float32) {.simd.} =
let opacity = round(255 * opacity).uint16
if opacity == 255:
return
if opacity == 0:
nimSetMem(mask.data[0].addr, 0.cint, mask.data.len)
var
i: int
p = cast[uint](mask.data[0].addr)
let
oddMask = mm_set1_epi16(0xff00)
div255 = mm_set1_epi16(0x8081)
zeroVec = mm_setzero_si128()
opacityVec = mm_slli_epi16(mm_set1_epi16(opacity), 8)
iterations = mask.data.len div 16
for _ in 0 ..< iterations:
let values = mm_loadu_si128(cast[pointer](p))
if mm_movemask_epi8(mm_cmpeq_epi16(values, zeroVec)) != 0xffff:
var
valuesEven = mm_slli_epi16(values, 8)
valuesOdd = mm_and_si128(values, oddMask)
valuesEven = mm_mulhi_epu16(valuesEven, opacityVec)
valuesOdd = mm_mulhi_epu16(valuesOdd, opacityVec)
valuesEven = mm_srli_epi16(mm_mulhi_epu16(valuesEven, div255), 7)
valuesOdd = mm_srli_epi16(mm_mulhi_epu16(valuesOdd, div255), 7)
mm_storeu_si128(
cast[pointer](p),
mm_or_si128(valuesEven, mm_slli_epi16(valuesOdd, 8))
)
p += 16
i += 16 * iterations
for i in i ..< mask.data.len:
mask.data[i] = ((mask.data[i] * opacity) div 255).uint8
proc blitLineNormalSse2*(a, b: ptr UncheckedArray[ColorRGBX], len: int) {.simd.} =
proc blitLineNormalSse2*(
a, b: ptr UncheckedArray[ColorRGBX], len: int
) {.simd.} =
# TODO align to 16

View file

@ -6,7 +6,6 @@ import
test_images,
test_images_draw,
test_jpeg,
test_masks,
test_paints,
test_paths,
test_png,

View file

@ -7,7 +7,6 @@ font.size = 16
let
image = newImage(500, 300)
# mask = newMask(500, 300)
timeIt "typeset":
discard font.typeset(text, bounds = vec2(image.width.float32, 0))
@ -15,5 +14,3 @@ timeIt "typeset":
timeIt "fill text":
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, text, bounds = vec2(image.width.float32, 0))
# mask.fill(0)
# mask.fillText(font, text, bounds = mask.wh)

View file

@ -89,33 +89,6 @@ timeIt "toStraightAlpha":
reset()
block:
let path = newPath()
path.ellipse(image.width / 2, image.height / 2, 300, 300)
let mask = newMask(image.width, image.height)
mask.fillPath(path)
timeIt "mask":
image.draw(mask)
reset()
timeIt "newMask(image)":
let mask = image.newMask()
doAssert mask[0, 0] == image[0, 0].a
reset()
block:
let mask = image.newMask()
timeIt "newImage(mask)":
let image = newImage(mask)
doAssert mask[0, 0] == image[0, 0].a
reset()
block:
let image = newImage(200, 200)
image.fill(rgbx(255, 0, 0, 255))

View file

@ -77,17 +77,6 @@ block:
a.draw(b, translate(vec2(0, 500)) * rotate(toRadians(45)), NormalBlend)
keep(b)
block:
let
a = newImage(1000, 1000)
b = newMask(500, 500)
a.fill(rgba(255, 0, 0, 255))
b.fill(rand(255).uint8)
timeIt "mask smooth rotate 45 deg":
a.draw(b, translate(vec2(0, 500)) * rotate(toRadians(45)), NormalBlend)
keep(b)
block:
let
a = newImage(100, 100)

View file

@ -1,5 +1,4 @@
import pixie, strformat, xrays
import pixie/images {.all.}
import benchy, pixie, pixie/images {.all.}, strformat, xrays
block:
let
@ -11,8 +10,6 @@ block:
a.drawCorrect(b, translate(vec2(250, 250)), blendMode = OverwriteBlend)
a.writeFile("tests/images/rotate0.png")
import benchy
block:
let
a = newImage(1000, 1000)
@ -24,7 +21,6 @@ block:
a.drawCorrect(b, translate(vec2(250, 250)), blendMode = OverwriteBlend)
block:
let
a = newImage(1000, 1000)

View file

@ -1,4 +1,4 @@
import benchy, pixie/fileformats/jpeg, os
import benchy, os, pixie/fileformats/jpeg
const
jpegFiles* = [

View file

@ -1,71 +0,0 @@
import benchy, pixie
block:
let mask = newMask(2560, 1440)
proc reset() =
mask.fill(63)
reset()
timeIt "minifyBy2":
let minified = mask.minifyBy2()
doAssert minified[0, 0] == 63
reset()
timeIt "magnifyBy2":
let magnified = mask.magnifyBy2()
doAssert magnified[0, 0] == 63
reset()
timeIt "invert":
mask.invert()
reset()
timeIt "applyOpacity":
reset()
mask.applyOpacity(0.5)
reset()
timeIt "ceil":
mask.ceil()
block:
let mask = newMask(400, 400)
mask.fill(63)
timeIt "blur":
mask.blur(12)
block spread_1:
let p = newPath()
p.rect(100, 100, 200, 200)
timeIt "spread_1":
mask.fill(0)
mask.fillPath(p)
mask.spread(5)
block spread_2:
let p = newPath()
p.rect(100, 100, 200, 200)
timeIt "spread_2":
mask.fill(0)
mask.fillPath(p)
mask.spread(10)
block spread_3:
timeIt "spread_3":
mask.fill(255)
mask.spread(10)
block spread_4:
timeIt "spread_4":
mask.fill(0)
mask.unsafe[200, 200] = 255
mask.spread(5)

View file

@ -31,90 +31,50 @@ let roundedRect = newPath()
roundedRect.roundedRect(10.5, 10.5, 479, 279, radius, radius, radius, radius)
# roundedRect.roundedRect(10, 10, 480, 280, radius, radius, radius, radius)
block:
let image = newImage(width, height)
let image = newImage(width, height)
timeIt "rect Image OverwriteBlend":
paint.blendMode = OverwriteBlend
image.fillPath(rect, paint)
timeIt "rect OverwriteBlend":
paint.blendMode = OverwriteBlend
image.fillPath(rect, paint)
timeIt "rect Image NormalBlend":
paint.blendMode = NormalBlend
image.fillPath(rect, paint)
timeIt "rect NormalBlend":
paint.blendMode = NormalBlend
image.fillPath(rect, paint)
timeIt "rect Image MaskBlend":
paint.blendMode = MaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect MaskBlend":
paint.blendMode = MaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect Image SubtractMaskBlend":
paint.blendMode = SubtractMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect SubtractMaskBlend":
paint.blendMode = SubtractMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect Image ExcludeMaskBlend":
paint.blendMode = ExcludeMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect ExcludeMaskBlend":
paint.blendMode = ExcludeMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "roundedRect Image OverwriteBlend":
paint.blendMode = OverwriteBlend
image.fillPath(roundedRect, paint)
timeIt "roundedRect OverwriteBlend":
paint.blendMode = OverwriteBlend
image.fillPath(roundedRect, paint)
timeIt "roundedRect Image NormalBlend":
paint.blendMode = NormalBlend
image.fillPath(roundedRect, paint)
timeIt "roundedRect NormalBlend":
paint.blendMode = NormalBlend
image.fillPath(roundedRect, paint)
timeIt "roundedRect Image MaskBlend":
paint.blendMode = MaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
timeIt "roundedRect MaskBlend":
paint.blendMode = MaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
timeIt "roundedRect Image SubtractMaskBlend":
paint.blendMode = SubtractMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
timeIt "roundedRect SubtractMaskBlend":
paint.blendMode = SubtractMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
timeIt "roundedRect Image ExcludeMaskBlend":
paint.blendMode = ExcludeMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
block:
let mask = newMask(width, height)
timeIt "rect Mask OverwriteBlend":
mask.fillPath(rect, blendMode = OverwriteBlend)
timeIt "rect Mask NormalBlend":
mask.fillPath(rect, blendMode = NormalBlend)
timeIt "rect Mask MaskBlend":
mask.fill(255)
mask.fillPath(rect, blendMode = MaskBlend)
timeIt "rect Mask SubtractMaskBlend":
mask.fill(255)
mask.fillPath(rect, blendMode = SubtractMaskBlend)
timeIt "rect Mask ExcludeMaskBlend":
mask.fill(255)
mask.fillPath(rect, blendMode = ExcludeMaskBlend)
timeIt "roundedRect Mask OverwriteBlend":
mask.fillPath(roundedRect, blendMode = OverwriteBlend)
timeIt "roundedRect Mask NormalBlend":
mask.fillPath(roundedRect, blendMode = NormalBlend)
timeIt "roundedRect Mask MaskBlend":
mask.fill(255)
mask.fillPath(roundedRect, blendMode = MaskBlend)
timeIt "roundedRect Mask SubtractMaskBlend":
mask.fill(255)
mask.fillPath(roundedRect, blendMode = SubtractMaskBlend)
timeIt "roundedRect Mask ExcludeMaskBlend":
mask.fill(255)
mask.fillPath(roundedRect, blendMode = ExcludeMaskBlend)
timeIt "roundedRect ExcludeMaskBlend":
paint.blendMode = ExcludeMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)

Binary file not shown.

Before

(image error) Size: 662 B

Binary file not shown.

Before

(image error) Size: 3.2 KiB

Binary file not shown.

Before

(image error) Size: 2.2 KiB

After

(image error) Size: 2.8 KiB

Binary file not shown.

Before

(image error) Size: 2.3 KiB

After

(image error) Size: 3 KiB

Binary file not shown.

Before

(image error) Size: 1.2 KiB

After

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 2.5 KiB

After

(image error) Size: 3.2 KiB

Binary file not shown.

Before

(image error) Size: 1.8 KiB

After

(image error) Size: 2.3 KiB

Binary file not shown.

Before

(image error) Size: 868 B

Binary file not shown.

Before

(image error) Size: 9.6 KiB

After

(image error) Size: 6.7 KiB

Binary file not shown.

Before

(image error) Size: 1.6 KiB

Binary file not shown.

Before

(image error) Size: 555 B

Binary file not shown.

Before

(image error) Size: 859 B

Binary file not shown.

Before

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 329 B

Binary file not shown.

Before

(image error) Size: 732 B

Binary file not shown.

Before

(image error) Size: 693 B

Binary file not shown.

Before

(image error) Size: 1.2 KiB

Binary file not shown.

Before

(image error) Size: 590 B

Binary file not shown.

Before

(image error) Size: 353 B

Binary file not shown.

Before

(image error) Size: 1.2 KiB

Binary file not shown.

Before

(image error) Size: 2.3 KiB

Binary file not shown.

Before

(image error) Size: 329 B

Binary file not shown.

Before

(image error) Size: 324 B

Binary file not shown.

Before

(image error) Size: 1.5 KiB

Binary file not shown.

Before

(image error) Size: 1.9 KiB

Binary file not shown.

Before

(image error) Size: 348 B

Binary file not shown.

Before

(image error) Size: 1.2 KiB

Binary file not shown.

Before

(image error) Size: 356 B

Binary file not shown.

Before

(image error) Size: 383 B

Binary file not shown.

Before

(image error) Size: 328 B

Binary file not shown.

Before

(image error) Size: 341 B

Binary file not shown.

Before

(image error) Size: 347 B

Binary file not shown.

Before

(image error) Size: 326 B

Binary file not shown.

Before

(image error) Size: 627 B

Binary file not shown.

Before

(image error) Size: 980 B

After

(image error) Size: 930 B

Binary file not shown.

Before

(image error) Size: 657 B

After

(image error) Size: 653 B

Binary file not shown.

Before

(image error) Size: 1.1 KiB

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1.1 KiB

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1.2 KiB

After

(image error) Size: 1.2 KiB

Binary file not shown.

Before

(image error) Size: 1.4 KiB

After

(image error) Size: 1.3 KiB

View file

@ -1,4 +1,4 @@
import os, pixie, pixie/fileformats/png, strformat, unicode, xrays
import os, pixie, strformat, unicode, xrays
proc wh(image: Image): Vec2 =
## Return with and height as a size vector.
@ -36,58 +36,42 @@ block:
image.xray("tests/fonts/masters/image_stroke.png")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 64
let mask = newMask(200, 100)
mask.fillText(font, "fill")
mask.xray("tests/fonts/masters/mask_fill.png")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 64
let mask = newMask(200, 100)
mask.strokeText(font, "stroke")
mask.xray("tests/fonts/masters/mask_stroke.png")
block:
# SVG Fonts have no masters
block:
var font = readFont("tests/fonts/Changa-Bold.svg")
font.size = 48
let mask = newMask(200, 100)
mask.fillText(font, "Changa")
mask.xray("tests/fonts/svg_changa.png")
let image = newImage(200, 100)
image.fillText(font, "Changa")
image.xray("tests/fonts/svg_changa.png")
block:
var font = readFont("tests/fonts/DejaVuSans.svg")
font.size = 48
let mask = newMask(200, 100)
mask.fillText(font, "Deja vu ")
mask.xray("tests/fonts/svg_dejavu.png")
let image = newImage(200, 100)
image.fillText(font, "Deja vu ")
image.xray("tests/fonts/svg_dejavu.png")
block:
var font = readFont("tests/fonts/IBMPlexSans-Regular.svg")
font.size = 48
let mask = newMask(200, 100)
mask.fillText(font, "IBM ")
mask.xray("tests/fonts/svg_ibm.png")
let image = newImage(200, 100)
image.fillText(font, "IBM ")
image.xray("tests/fonts/svg_ibm.png")
block:
var font = readFont("tests/fonts/Moon-Bold.svg")
font.size = 48
let mask = newMask(200, 100)
mask.fillText(font, "Moon ")
mask.xray("tests/fonts/svg_moon.png")
let image = newImage(200, 100)
image.fillText(font, "Moon ")
image.xray("tests/fonts/svg_moon.png")
block:
var font = readFont("tests/fonts/Ubuntu.svg")
font.size = 48
let mask = newMask(200, 100)
mask.fillText(font, "Ubuntu ")
mask.xray("tests/fonts/svg_ubuntu.png")
let image = newImage(200, 100)
image.fillText(font, "Ubuntu ")
image.xray("tests/fonts/svg_ubuntu.png")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")

View file

@ -139,32 +139,6 @@ block:
ctx.image.blur(20, rgba(0, 0, 0, 255))
ctx.image.xray("tests/images/imageblur20oob.png")
block: # Test conversion between image and mask
let
originalImage = newImage(100, 100)
originalMask = newMask(100, 100)
let p = newPath()
p.circle(50, 50, 25)
originalImage.fillPath(p, rgba(255, 0, 0, 255))
originalMask.fillPath(p)
# # Converting an image to a mask == a mask of the same fill
# doAssert newMask(originalImage).data == originalMask.data
# # Converting a mask to an image == converting an image to a mask as an image
# doAssert newImage(newMask(originalImage)).data == newImage(originalMask).data
block:
let p = newPath()
p.roundedRect(10, 10, 80, 80, 10, 10, 10, 10)
let image = newImage(100, 100)
image.fillPath(p, rgba(255, 0, 0, 255))
newImage(newMask(image)).xray("tests/images/mask2image.png")
block:
let image = newImage(100, 100)
doAssert image.isOneColor()

View file

@ -1,217 +0,0 @@
import pixie, xrays
block:
let mask = newMask(100, 100)
mask.fill(200)
mask.applyOpacity(0.5)
doAssert mask[0, 0] == 100
doAssert mask[88, 88] == 100
block:
let mask = newMask(100, 100)
mask.fill(200)
mask.invert()
doAssert mask[0, 0] == 55
block:
let
mask = newMask(100, 100)
r = 10.0
x = 10.0
y = 10.0
h = 80.0
w = 80.0
let path = newPath()
path.moveTo(x + r, y)
# path.arcTo(x + w, y, x + w, y + h, r)
# path.arcTo(x + w, y + h, x, y + h, r)
# path.arcTo(x, y + h, x, y, r)
# path.arcTo(x, y, x + w, y, r)
path.roundedRect(x, y, w, h, r, r, r, r)
mask.fillPath(path)
let minified = mask.minifyBy2()
doAssert minified.width == 50 and minified.height == 50
minified.xray("tests/masks/maskMinified.png")
block:
let
a = readImage("tests/masks/maskMinified.png")
b = a.magnifyBy2()
b.xray("tests/masks/maskMagnified.png")
block:
let image = newImage(100, 100)
image.fill(rgba(255, 100, 100, 255))
let path = newPath()
path.ellipse(image.width / 2, image.height / 2, 25, 25)
let mask = newMask(image.width, image.height)
mask.fillPath(path)
image.draw(mask)
image.xray("tests/masks/circleMask.png")
block:
let a = newMask(100, 100)
a.fill(255)
let path = newPath()
path.ellipse(a.width / 2, a.height / 2, 25, 25)
let b = newMask(a.width, a.height)
b.fillPath(path)
a.draw(b)
a.xray("tests/masks/maskedMask.png")
block:
let a = newMask(100, 100)
a.fill(255)
let path = newPath()
path.ellipse(a.width / 2, a.height / 2, 25, 25)
let b = newImage(a.width, a.height)
b.fillPath(path, rgba(0, 0, 0, 255))
a.draw(b)
a.xray("tests/masks/imageMaskedMask.png")
block:
let path = newPath()
path.rect(40, 40, 20, 20)
let a = newMask(100, 100)
a.fillPath(path)
a.spread(10)
a.xray("tests/masks/spread.png")
block:
let path = newPath()
path.rect(40, 40, 20, 20)
let a = newMask(100, 100)
a.fillPath(path)
a.spread(-5)
a.xray("tests/masks/negativeSpread.png")
block:
let mask = newMask(100, 100)
let path = newPath()
path.ellipse(mask.width / 2, mask.height / 2, 25, 25)
mask.fillPath(path)
mask.ceil()
mask.xray("tests/masks/circleMaskSharpened.png")
block:
let path = newPath()
path.rect(rect(vec2(10, 10), vec2(30, 30)))
let mask = newMask(100, 100)
mask.fillPath(path)
mask.xray("tests/masks/drawRect.png")
block:
let path = newPath()
path.rect(rect(vec2(10, 10), vec2(30, 30)))
let mask = newMask(100, 100)
mask.strokePath(path, strokeWidth = 10)
mask.xray("tests/masks/strokeRect.png")
block:
let path = newPath()
path.roundedRect(rect(vec2(10, 10), vec2(30, 30)), 10, 10, 10, 10)
let mask = newMask(100, 100)
mask.fillPath(path)
mask.xray("tests/masks/drawRoundedRect.png")
block:
let path = newPath()
path.roundedRect(rect(vec2(10, 10), vec2(30, 30)), 10, 10, 10, 10)
let mask = newMask(100, 100)
mask.strokePath(path, strokeWidth = 10)
mask.xray("tests/masks/strokeRoundedRect.png")
block:
let path = newPath()
path.moveTo(vec2(10, 10))
path.lineTo(vec2(90, 90))
let mask = newMask(100, 100)
mask.strokePath(path, strokeWidth = 10)
mask.xray("tests/masks/drawSegment.png")
block:
let path = newPath()
path.ellipse(vec2(50, 50), 20, 10)
let mask = newMask(100, 100)
mask.fillPath(path)
mask.xray("tests/masks/drawEllipse.png")
block:
let path = newPath()
path.ellipse(vec2(50, 50), 20, 10)
let mask = newMask(100, 100)
mask.strokePath(path, strokeWidth = 10)
mask.xray("tests/masks/strokeEllipse.png")
block:
let path = newPath()
path.polygon(vec2(50, 50), 30, 6)
let mask = newMask(100, 100)
mask.fillPath(path)
mask.xray("tests/masks/drawPolygon.png")
block:
let path = newPath()
path.polygon(vec2(50, 50), 30, 6)
let mask = newMask(100, 100)
mask.strokepath(path, strokeWidth = 10)
mask.xray("tests/masks/strokePolygon.png")
block:
let path = newPath()
path.rect(rect(25, 25, 50, 50))
let mask = newMask(100, 100)
mask.fillpath(path)
mask.blur(20)
mask.xray("tests/images/maskblur20.png")
block:
let path = newPath()
path.rect(rect(25, 25, 150, 150))
let mask = newMask(200, 200)
mask.fillPath(path)
mask.blur(25)
let minified = mask.minifyBy2()
minified.xray("tests/masks/minifiedBlur.png")
block:
let path = newPath()
path.polygon(vec2(50, 50), 30, 6)
let mask = newMask(100, 100)
mask.fillPath(path)
let magnified = mask.magnifyBy2()
magnified.xray("tests/masks/drawPolygonMagnified.png")

View file

@ -172,30 +172,6 @@ block:
# image.fillPath(path, rgba(255, 0, 0, 255))
# image.xray("tests/paths/pathRoundRect.png")
block:
let
mask = newMask(100, 100)
pathStr = "M 10 10 H 90 V 90 H 10 L 10 10"
mask.fillPath(pathStr)
mask.xray("tests/paths/pathRectangleMask.png")
# block:
# let
# mask = newMask(100, 100)
# r = 10.0
# x = 10.0
# y = 10.0
# h = 80.0
# w = 80.0
# let path = newPath()
# path.moveTo(x + r, y)
# path.arcTo(x + w, y, x + w, y + h, r)
# path.arcTo(x + w, y + h, x, y + h, r)
# path.arcTo(x, y + h, x, y, r)
# path.arcTo(x, y, x + w, y, r)
# mask.fillPath(path)
# writeFile("tests/paths/pathRoundRectMask.png", mask.encodePng())
block:
let image = newImage(200, 200)
image.fill(rgba(255, 255, 255, 255))
@ -469,43 +445,6 @@ block:
)
image.xray("tests/paths/rectMaskStroke.png")
block:
let mask = newMask(100, 100)
mask.fillPath("M 10 10 H 60 V 60 H 10 z")
mask.fillPath("M 30 30 H 80 V 80 H 30 z", blendMode = ExcludeMaskBlend)
mask.xray("tests/paths/maskRectExcludeMask.png")
block:
let mask = newMask(100, 100)
mask.fillPath("M 10.1 10.1 H 60.1 V 60.1 H 10.1 z")
mask.fillPath(
"M 30.1 30.1 H 80.1 V 80.1 H 30.1 z",
blendMode = ExcludeMaskBlend
)
mask.xray("tests/paths/maskRectExcludeMaskAA.png")
block:
let mask = newMask(100, 100)
mask.fillPath("M 10 10 H 60 V 60 H 10 z")
mask.fillPath("M 30 30 H 80 V 80 H 30 z", blendMode = MaskBlend)
mask.xray("tests/paths/maskRectMask.png")
block:
let mask = newMask(100, 100)
mask.fillPath("M 10.1 10.1 H 60.1 V 60.1 H 10.1 z")
mask.fillPath("M 30.1 30.1 H 80.1 V 80.1 H 30.1 z", blendMode = MaskBlend)
mask.xray("tests/paths/maskRectMaskAA.png")
block:
let mask = newMask(100, 100)
mask.fillPath("M 10 10 H 60 V 60 H 10 z")
mask.strokePath(
"M 30 30 H 50 V 50 H 30 z",
strokeWidth = 10,
blendMode = MaskBlend
)
mask.xray("tests/paths/maskStrokeRectMask.png")
block:
var
surface = newImage(256, 256)
@ -674,13 +613,6 @@ block:
image.strokePath(pathStr, color, strokeWidth = 10)
image.xray("tests/paths/pathStroke1Big.png")
block:
let
image = newMask(100, 100)
pathStr = "M0 0 L200 200"
image.strokePath(pathStr, strokeWidth = 10)
image.xray("tests/paths/pathStroke1BigMask.png")
block:
let
image = newImage(100, 100)
@ -715,21 +647,14 @@ block:
color = rgba(255, 0, 0, 255)
image.fillPath(pathStr, color)
block:
# Test zero width mask fill.
let
mask = newMask(100, 100)
pathStr = "M0 0 L0 1 L0 0 Z"
mask.fillPath(pathStr)
block:
# Test different polygons.
for i in 3 .. 8:
let path = newPath()
path.polygon(vec2(50, 50), 30, i)
let mask = newMask(100, 100)
mask.fillPath(path)
mask.xray(&"tests/paths/polygon{i}.png")
let image = newImage(100, 100)
image.fillPath(path, color(1, 1, 1, 1))
image.xray(&"tests/paths/polygon{i}.png")
block:
let image = newImage(200, 200)

View file

@ -1,4 +1,4 @@
import pixie, strformat, os, strutils
import os, pixie, strformat, strutils
proc xray*(image: Image, masterPath: string) =
let
@ -12,6 +12,3 @@ proc xray*(image: Image, masterPath: string) =
(score, xRay) = diff(image, master)
xRay.writeFile(xrayPath)
echo &"xray {masterPath} -> {score:0.6f}"
proc xray*(mask: Mask, masterPath: string) =
mask.newImage.xray(masterPath)