Merge pull request #479 from treeform/guzba
delete mask ref object type
|
@ -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)
|
||||
```
|
||||

|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Before ![]() (image error) Size: 88 KiB After ![]() (image error) Size: 91 KiB ![]() ![]() |
Before ![]() (image error) Size: 10 KiB After ![]() (image error) Size: 9.3 KiB ![]() ![]() |
Before ![]() (image error) Size: 4.6 KiB After ![]() (image error) Size: 3.6 KiB ![]() ![]() |
Before ![]() (image error) Size: 40 KiB After ![]() (image error) Size: 40 KiB ![]() ![]() |
Before ![]() (image error) Size: 2.4 KiB After ![]() (image error) Size: 1.3 KiB ![]() ![]() |
|
@ -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")
|
||||
|
|
Before ![]() (image error) Size: 5.9 KiB After ![]() (image error) Size: 4.8 KiB ![]() ![]() |
Before ![]() (image error) Size: 2.2 KiB After ![]() (image error) Size: 1.7 KiB ![]() ![]() |
Before ![]() (image error) Size: 9.2 KiB After ![]() (image error) Size: 9.5 KiB ![]() ![]() |
Before ![]() (image error) Size: 914 B After ![]() (image error) Size: 629 B ![]() ![]() |
Before ![]() (image error) Size: 20 KiB After ![]() (image error) Size: 17 KiB ![]() ![]() |
Before ![]() (image error) Size: 18 KiB After ![]() (image error) Size: 15 KiB ![]() ![]() |
Before ![]() (image error) Size: 42 KiB After ![]() (image error) Size: 40 KiB ![]() ![]() |
|
@ -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:
|
||||
|
|
|
@ -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.}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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.}
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import
|
|||
test_images,
|
||||
test_images_draw,
|
||||
test_jpeg,
|
||||
test_masks,
|
||||
test_paints,
|
||||
test_paths,
|
||||
test_png,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import benchy, pixie/fileformats/jpeg, os
|
||||
import benchy, os, pixie/fileformats/jpeg
|
||||
|
||||
const
|
||||
jpegFiles* = [
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
Before ![]() (image error) Size: 662 B |
Before ![]() (image error) Size: 3.2 KiB |
Before ![]() (image error) Size: 2.2 KiB After ![]() (image error) Size: 2.8 KiB ![]() ![]() |
Before ![]() (image error) Size: 2.3 KiB After ![]() (image error) Size: 3 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.2 KiB After ![]() (image error) Size: 1.7 KiB ![]() ![]() |
Before ![]() (image error) Size: 2.5 KiB After ![]() (image error) Size: 3.2 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.8 KiB After ![]() (image error) Size: 2.3 KiB ![]() ![]() |
Before ![]() (image error) Size: 868 B |
Before ![]() (image error) Size: 9.6 KiB After ![]() (image error) Size: 6.7 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.6 KiB |
Before ![]() (image error) Size: 555 B |
Before ![]() (image error) Size: 859 B |
Before ![]() (image error) Size: 1.1 KiB |
Before ![]() (image error) Size: 1.7 KiB |
Before ![]() (image error) Size: 329 B |
Before ![]() (image error) Size: 732 B |
Before ![]() (image error) Size: 693 B |
Before ![]() (image error) Size: 1.2 KiB |
Before ![]() (image error) Size: 590 B |
Before ![]() (image error) Size: 353 B |
Before ![]() (image error) Size: 1.2 KiB |
Before ![]() (image error) Size: 2.3 KiB |
Before ![]() (image error) Size: 329 B |
Before ![]() (image error) Size: 324 B |
Before ![]() (image error) Size: 1.5 KiB |
Before ![]() (image error) Size: 1.9 KiB |
Before ![]() (image error) Size: 348 B |
Before ![]() (image error) Size: 1.2 KiB |
Before ![]() (image error) Size: 356 B |
Before ![]() (image error) Size: 383 B |
Before ![]() (image error) Size: 328 B |
Before ![]() (image error) Size: 341 B |
Before ![]() (image error) Size: 347 B |
Before ![]() (image error) Size: 326 B |
Before ![]() (image error) Size: 627 B |
Before ![]() (image error) Size: 980 B After ![]() (image error) Size: 930 B ![]() ![]() |
Before ![]() (image error) Size: 657 B After ![]() (image error) Size: 653 B ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 1.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 1.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.2 KiB After ![]() (image error) Size: 1.2 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.4 KiB After ![]() (image error) Size: 1.3 KiB ![]() ![]() |
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|