diff --git a/README.md b/README.md index a85598b..d5128be 100644 --- a/README.md +++ b/README.md @@ -223,9 +223,10 @@ mask.fillPath( Q 180 120 100 180 Q 20 120 20 60 z - """ + """, + color(1, 1, 1, 1) ) -lines.draw(mask) +lines.draw(mask, blendMode = MaskBlend) image.draw(lines) ``` ![example output](examples/masking.png) @@ -303,8 +304,8 @@ nim c -r [examples/blur.nim](examples/blur.nim) let path = newPath() path.polygon(vec2(100, 100), 70, sides = 6) -let mask = newMask(200, 200) -mask.fillPath(path) +let mask = newImage(200, 200) +mask.fillPath(path, color(1, 1, 1, 1)) blur.blur(20) blur.draw(mask, blendMode = MaskBlend) diff --git a/bindings/bindings.nim b/bindings/bindings.nim index 8b18019..7ad38fd 100644 --- a/bindings/bindings.nim +++ b/bindings/bindings.nim @@ -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 diff --git a/examples/blur.nim b/examples/blur.nim index 9de0ea0..589e41b 100644 --- a/examples/blur.nim +++ b/examples/blur.nim @@ -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) diff --git a/examples/blur.png b/examples/blur.png index cc5f750..3998536 100644 Binary files a/examples/blur.png and b/examples/blur.png differ diff --git a/examples/gradient.png b/examples/gradient.png index de84639..b6d7a37 100644 Binary files a/examples/gradient.png and b/examples/gradient.png differ diff --git a/examples/heart.png b/examples/heart.png index 79ca8af..25d0f6b 100644 Binary files a/examples/heart.png and b/examples/heart.png differ diff --git a/examples/image_tiled.png b/examples/image_tiled.png index 9d5a7a4..5c955bd 100644 Binary files a/examples/image_tiled.png and b/examples/image_tiled.png differ diff --git a/examples/line.png b/examples/line.png index 42eddfa..6ebe0e6 100644 Binary files a/examples/line.png and b/examples/line.png differ diff --git a/examples/masking.nim b/examples/masking.nim index 535c10b..db9bf74 100644 --- a/examples/masking.nim +++ b/examples/masking.nim @@ -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") diff --git a/examples/masking.png b/examples/masking.png index e98964e..74c9507 100644 Binary files a/examples/masking.png and b/examples/masking.png differ diff --git a/examples/rounded_rectangle.png b/examples/rounded_rectangle.png index a8bf4d1..8b72368 100644 Binary files a/examples/rounded_rectangle.png and b/examples/rounded_rectangle.png differ diff --git a/examples/shadow.png b/examples/shadow.png index 71469c9..eebc82a 100644 Binary files a/examples/shadow.png and b/examples/shadow.png differ diff --git a/examples/square.png b/examples/square.png index 9d011d9..388cbb6 100644 Binary files a/examples/square.png and b/examples/square.png differ diff --git a/examples/text.png b/examples/text.png index 055a020..890dba1 100644 Binary files a/examples/text.png and b/examples/text.png differ diff --git a/examples/text_spans.png b/examples/text_spans.png index dcd304c..81788a1 100644 Binary files a/examples/text_spans.png and b/examples/text_spans.png differ diff --git a/examples/tiger.png b/examples/tiger.png index 765c596..c55514c 100644 Binary files a/examples/tiger.png and b/examples/tiger.png differ diff --git a/src/pixie.nim b/src/pixie.nim index bb7ad28..f4a1004 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -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: diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 2694c73..484e9b3 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -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.} diff --git a/src/pixie/common.nim b/src/pixie/common.nim index 94f4563..8b04458 100644 --- a/src/pixie/common.nim +++ b/src/pixie/common.nim @@ -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 diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index ee2d0d1..8323fbe 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -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 diff --git a/src/pixie/fileformats/gif.nim b/src/pixie/fileformats/gif.nim index a061d6e..88d0d2e 100644 --- a/src/pixie/fileformats/gif.nim +++ b/src/pixie/fileformats/gif.nim @@ -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): diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index 4fe5980..852eb20 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -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: diff --git a/src/pixie/fileformats/png.nim b/src/pixie/fileformats/png.nim index 9877d7a..f53bfaf 100644 --- a/src/pixie/fileformats/png.nim +++ b/src/pixie/fileformats/png.nim @@ -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.} diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 28bd41a..570be0c 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -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(), diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 9e61f9f..6fc99f4 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -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. diff --git a/src/pixie/internal.nim b/src/pixie/internal.nim index e3466b2..3f00fca 100644 --- a/src/pixie/internal.nim +++ b/src/pixie/internal.nim @@ -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]] diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim deleted file mode 100644 index 9fd8438..0000000 --- a/src/pixie/masks.nim +++ /dev/null @@ -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. - "" - -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.} diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index c0a20e5..d296bbb 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -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( diff --git a/src/pixie/simd.nim b/src/pixie/simd.nim index f039b1a..57b0b8d 100644 --- a/src/pixie/simd.nim +++ b/src/pixie/simd.nim @@ -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) diff --git a/src/pixie/simd/neon.nim b/src/pixie/simd/neon.nim index 19fa0a3..e28572e 100644 --- a/src/pixie/simd/neon.nim +++ b/src/pixie/simd/neon.nim @@ -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.} diff --git a/src/pixie/simd/sse2.nim b/src/pixie/simd/sse2.nim index 5724cfd..313c756 100644 --- a/src/pixie/simd/sse2.nim +++ b/src/pixie/simd/sse2.nim @@ -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 diff --git a/tests/all.nim b/tests/all.nim index 53f7b84..a553833 100644 --- a/tests/all.nim +++ b/tests/all.nim @@ -6,7 +6,6 @@ import test_images, test_images_draw, test_jpeg, - test_masks, test_paints, test_paths, test_png, diff --git a/tests/bench_fonts.nim b/tests/bench_fonts.nim index 6d7df49..32bbc01 100644 --- a/tests/bench_fonts.nim +++ b/tests/bench_fonts.nim @@ -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) diff --git a/tests/bench_images.nim b/tests/bench_images.nim index b99c3eb..28ab7aa 100644 --- a/tests/bench_images.nim +++ b/tests/bench_images.nim @@ -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)) diff --git a/tests/bench_images_draw.nim b/tests/bench_images_draw.nim index a560100..ed42281 100644 --- a/tests/bench_images_draw.nim +++ b/tests/bench_images_draw.nim @@ -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) diff --git a/tests/bench_images_draw_correct.nim b/tests/bench_images_draw_correct.nim index bed3c6f..93a27a0 100644 --- a/tests/bench_images_draw_correct.nim +++ b/tests/bench_images_draw_correct.nim @@ -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) diff --git a/tests/bench_jpeg.nim b/tests/bench_jpeg.nim index 6c79c6d..2885216 100644 --- a/tests/bench_jpeg.nim +++ b/tests/bench_jpeg.nim @@ -1,4 +1,4 @@ -import benchy, pixie/fileformats/jpeg, os +import benchy, os, pixie/fileformats/jpeg const jpegFiles* = [ diff --git a/tests/bench_masks.nim b/tests/bench_masks.nim deleted file mode 100644 index c3f21c3..0000000 --- a/tests/bench_masks.nim +++ /dev/null @@ -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) diff --git a/tests/bench_paths.nim b/tests/bench_paths.nim index f890b48..7f5a66f 100644 --- a/tests/bench_paths.nim +++ b/tests/bench_paths.nim @@ -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) diff --git a/tests/fonts/masters/mask_fill.png b/tests/fonts/masters/mask_fill.png deleted file mode 100644 index 670bf7e..0000000 Binary files a/tests/fonts/masters/mask_fill.png and /dev/null differ diff --git a/tests/fonts/masters/mask_stroke.png b/tests/fonts/masters/mask_stroke.png deleted file mode 100644 index 16b78f5..0000000 Binary files a/tests/fonts/masters/mask_stroke.png and /dev/null differ diff --git a/tests/fonts/svg_changa.png b/tests/fonts/svg_changa.png index 3b938f9..30889ca 100644 Binary files a/tests/fonts/svg_changa.png and b/tests/fonts/svg_changa.png differ diff --git a/tests/fonts/svg_dejavu.png b/tests/fonts/svg_dejavu.png index 6dbf0a9..15e056d 100644 Binary files a/tests/fonts/svg_dejavu.png and b/tests/fonts/svg_dejavu.png differ diff --git a/tests/fonts/svg_ibm.png b/tests/fonts/svg_ibm.png index 4363401..de11b85 100644 Binary files a/tests/fonts/svg_ibm.png and b/tests/fonts/svg_ibm.png differ diff --git a/tests/fonts/svg_moon.png b/tests/fonts/svg_moon.png index 06ce857..d17ef05 100644 Binary files a/tests/fonts/svg_moon.png and b/tests/fonts/svg_moon.png differ diff --git a/tests/fonts/svg_ubuntu.png b/tests/fonts/svg_ubuntu.png index 2f33818..c69f5f6 100644 Binary files a/tests/fonts/svg_ubuntu.png and b/tests/fonts/svg_ubuntu.png differ diff --git a/tests/images/mask2image.png b/tests/images/mask2image.png deleted file mode 100644 index 1f97303..0000000 Binary files a/tests/images/mask2image.png and /dev/null differ diff --git a/tests/images/rotate0.png b/tests/images/rotate0.png index 013b002..75f2ec4 100644 Binary files a/tests/images/rotate0.png and b/tests/images/rotate0.png differ diff --git a/tests/masks/circleMask.png b/tests/masks/circleMask.png deleted file mode 100644 index c082edc..0000000 Binary files a/tests/masks/circleMask.png and /dev/null differ diff --git a/tests/masks/circleMaskSharpened.png b/tests/masks/circleMaskSharpened.png deleted file mode 100644 index 8ca3c80..0000000 Binary files a/tests/masks/circleMaskSharpened.png and /dev/null differ diff --git a/tests/masks/drawEllipse.png b/tests/masks/drawEllipse.png deleted file mode 100644 index 1437bd5..0000000 Binary files a/tests/masks/drawEllipse.png and /dev/null differ diff --git a/tests/masks/drawPolygon.png b/tests/masks/drawPolygon.png deleted file mode 100644 index b4e0246..0000000 Binary files a/tests/masks/drawPolygon.png and /dev/null differ diff --git a/tests/masks/drawPolygonMagnified.png b/tests/masks/drawPolygonMagnified.png deleted file mode 100644 index 9538241..0000000 Binary files a/tests/masks/drawPolygonMagnified.png and /dev/null differ diff --git a/tests/masks/drawRect.png b/tests/masks/drawRect.png deleted file mode 100644 index 5daff86..0000000 Binary files a/tests/masks/drawRect.png and /dev/null differ diff --git a/tests/masks/drawRoundedRect.png b/tests/masks/drawRoundedRect.png deleted file mode 100644 index 59742ff..0000000 Binary files a/tests/masks/drawRoundedRect.png and /dev/null differ diff --git a/tests/masks/drawSegment.png b/tests/masks/drawSegment.png deleted file mode 100644 index 2fd9d15..0000000 Binary files a/tests/masks/drawSegment.png and /dev/null differ diff --git a/tests/masks/imageMaskedMask.png b/tests/masks/imageMaskedMask.png deleted file mode 100644 index 6d35719..0000000 Binary files a/tests/masks/imageMaskedMask.png and /dev/null differ diff --git a/tests/masks/maskMagnified.png b/tests/masks/maskMagnified.png deleted file mode 100644 index 4c0daab..0000000 Binary files a/tests/masks/maskMagnified.png and /dev/null differ diff --git a/tests/masks/maskMinified.png b/tests/masks/maskMinified.png deleted file mode 100644 index c9a386a..0000000 Binary files a/tests/masks/maskMinified.png and /dev/null differ diff --git a/tests/masks/maskedMask.png b/tests/masks/maskedMask.png deleted file mode 100644 index 74632bb..0000000 Binary files a/tests/masks/maskedMask.png and /dev/null differ diff --git a/tests/masks/minifiedBlur.png b/tests/masks/minifiedBlur.png deleted file mode 100644 index 935a34f..0000000 Binary files a/tests/masks/minifiedBlur.png and /dev/null differ diff --git a/tests/masks/negativeSpread.png b/tests/masks/negativeSpread.png deleted file mode 100644 index b695928..0000000 Binary files a/tests/masks/negativeSpread.png and /dev/null differ diff --git a/tests/masks/spread.png b/tests/masks/spread.png deleted file mode 100644 index 439a362..0000000 Binary files a/tests/masks/spread.png and /dev/null differ diff --git a/tests/masks/strokeEllipse.png b/tests/masks/strokeEllipse.png deleted file mode 100644 index 668f36d..0000000 Binary files a/tests/masks/strokeEllipse.png and /dev/null differ diff --git a/tests/masks/strokePolygon.png b/tests/masks/strokePolygon.png deleted file mode 100644 index f4acf83..0000000 Binary files a/tests/masks/strokePolygon.png and /dev/null differ diff --git a/tests/masks/strokeRect.png b/tests/masks/strokeRect.png deleted file mode 100644 index 395467b..0000000 Binary files a/tests/masks/strokeRect.png and /dev/null differ diff --git a/tests/masks/strokeRoundedRect.png b/tests/masks/strokeRoundedRect.png deleted file mode 100644 index c4a3f0c..0000000 Binary files a/tests/masks/strokeRoundedRect.png and /dev/null differ diff --git a/tests/paths/maskRectExcludeMask.png b/tests/paths/maskRectExcludeMask.png deleted file mode 100644 index 87be8ec..0000000 Binary files a/tests/paths/maskRectExcludeMask.png and /dev/null differ diff --git a/tests/paths/maskRectExcludeMaskAA.png b/tests/paths/maskRectExcludeMaskAA.png deleted file mode 100644 index 821e4b8..0000000 Binary files a/tests/paths/maskRectExcludeMaskAA.png and /dev/null differ diff --git a/tests/paths/maskRectMask.png b/tests/paths/maskRectMask.png deleted file mode 100644 index cb26a03..0000000 Binary files a/tests/paths/maskRectMask.png and /dev/null differ diff --git a/tests/paths/maskRectMaskAA.png b/tests/paths/maskRectMaskAA.png deleted file mode 100644 index 8f98469..0000000 Binary files a/tests/paths/maskRectMaskAA.png and /dev/null differ diff --git a/tests/paths/maskStrokeRectMask.png b/tests/paths/maskStrokeRectMask.png deleted file mode 100644 index eb6775f..0000000 Binary files a/tests/paths/maskStrokeRectMask.png and /dev/null differ diff --git a/tests/paths/pathRectangleMask.png b/tests/paths/pathRectangleMask.png deleted file mode 100644 index 1f06a58..0000000 Binary files a/tests/paths/pathRectangleMask.png and /dev/null differ diff --git a/tests/paths/pathStroke1BigMask.png b/tests/paths/pathStroke1BigMask.png deleted file mode 100644 index a5a5ea8..0000000 Binary files a/tests/paths/pathStroke1BigMask.png and /dev/null differ diff --git a/tests/paths/polygon3.png b/tests/paths/polygon3.png index efb46c9..81ef1e2 100644 Binary files a/tests/paths/polygon3.png and b/tests/paths/polygon3.png differ diff --git a/tests/paths/polygon4.png b/tests/paths/polygon4.png index eed18f1..de72a43 100644 Binary files a/tests/paths/polygon4.png and b/tests/paths/polygon4.png differ diff --git a/tests/paths/polygon5.png b/tests/paths/polygon5.png index d4392d7..4d6d5e3 100644 Binary files a/tests/paths/polygon5.png and b/tests/paths/polygon5.png differ diff --git a/tests/paths/polygon6.png b/tests/paths/polygon6.png index b4e0246..907fb0e 100644 Binary files a/tests/paths/polygon6.png and b/tests/paths/polygon6.png differ diff --git a/tests/paths/polygon7.png b/tests/paths/polygon7.png index f65888f..cbfe368 100644 Binary files a/tests/paths/polygon7.png and b/tests/paths/polygon7.png differ diff --git a/tests/paths/polygon8.png b/tests/paths/polygon8.png index 23f2849..1f8956c 100644 Binary files a/tests/paths/polygon8.png and b/tests/paths/polygon8.png differ diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index 632b94b..c4f608d 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -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") diff --git a/tests/test_images.nim b/tests/test_images.nim index 90ee301..70756cd 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -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() diff --git a/tests/test_masks.nim b/tests/test_masks.nim deleted file mode 100644 index a48c02b..0000000 --- a/tests/test_masks.nim +++ /dev/null @@ -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") diff --git a/tests/test_paths.nim b/tests/test_paths.nim index d452846..376ccfb 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -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) diff --git a/tests/xrays.nim b/tests/xrays.nim index e47a523..4bde00f 100644 --- a/tests/xrays.nim +++ b/tests/xrays.nim @@ -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)