diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 064e468..9977fff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,5 +13,6 @@ jobs: - uses: actions/checkout@v2 - uses: jiro4989/setup-nim-action@v1 - run: nimble test -d:release -y + - run: nimble test -d:release -d:pixieNoSimd -y - run: nimble test --gc:orc -d:release -y - run: nim cpp -d:release -r tests/all.nim diff --git a/pixie.nimble b/pixie.nimble index 57c4d10..973ada1 100644 --- a/pixie.nimble +++ b/pixie.nimble @@ -1,4 +1,4 @@ -version = "2.0.4" +version = "2.0.5" author = "Andre von Houck and Ryan Oldenburg" description = "Full-featured 2d graphics library for Nim." license = "MIT" diff --git a/src/pixie/images.nim b/src/pixie/images.nim index f5614a7..2ecbc0f 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -753,71 +753,72 @@ proc drawUber(a, b: Image | Mask, mat = mat3(), blendMode = bmNormal) = ) x += 16 - for _ in x ..< xMax: - let - srcPos = p + dx * x.float32 + dy * y.float32 - xFloat = srcPos.x - h - yFloat = srcPos.y - h + var srcPos = p + dx * x.float32 + dy * y.float32 + srcPos = vec2(max(0, srcPos.x), max(0, srcPos.y)) + + for x in x ..< xMax: + let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) when type(a) is Image: let backdrop = a.getRgbaUnsafe(x, y) when type(b) is Image: let - sample = b.getRgbaUnsafe(xFloat.int, yFloat.int) + sample = b.getRgbaUnsafe(samplePos.x, samplePos.y) blended = blender(backdrop, sample) else: # b is a Mask let - sample = b.getValueUnsafe(xFloat.int, yFloat.int) + sample = b.getValueUnsafe(samplePos.x, samplePos.y) blended = blender(backdrop, rgbx(0, 0, 0, sample)) a.setRgbaUnsafe(x, y, blended) else: # a is a Mask let backdrop = a.getValueUnsafe(x, y) when type(b) is Image: - let sample = b.getRgbaUnsafe(xFloat.int, yFloat.int).a + let sample = b.getRgbaUnsafe(samplePos.x, samplePos.y).a else: # b is a Mask - let sample = b.getValueUnsafe(xFloat.int, yFloat.int) + let sample = b.getValueUnsafe(samplePos.x, samplePos.y) a.setValueUnsafe(x, y, masker(backdrop, sample)) - inc x + + srcPos += dx if blendMode == bmIntersectMask: if a.width - xMax > 0: zeroMem(a.data[a.dataIndex(xMax, y)].addr, 4 * (a.width - xMax)) -proc draw*(a, b: Image, mat: Mat3, blendMode = bmNormal) {.inline.} = +proc draw*( + a, b: Image, transform: Vec2 | Mat3 = vec2(), blendMode = bmNormal +) {.inline.} = ## Draws one image onto another using matrix with color blending. - a.drawUber(b, mat, blendMode) - -proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} = - ## Draws one image onto another using a position offset with color blending. - a.draw(b, translate(pos), blendMode) - -proc draw*(image: Image, mask: Mask, mat: Mat3, blendMode = bmMask) {.inline.} = - ## Draws a mask onto an image using a matrix with color blending. - image.drawUber(mask, mat, blendMode) + when type(transform) is Vec2: + a.drawUber(b, translate(transform), blendMode) + else: + a.drawUber(b, transform, blendMode) proc draw*( - image: Image, mask: Mask, pos = vec2(0, 0), blendMode = bmMask + a, b: Mask, transform: Vec2 | Mat3 = vec2(), blendMode = bmMask ) {.inline.} = - ## Draws a mask onto an image using a position offset with color blending. - image.drawUber(mask, translate(pos), blendMode) - -proc draw*(a, b: Mask, mat: Mat3, blendMode = bmMask) {.inline.} = ## Draws a mask onto a mask using a matrix with color blending. - a.drawUber(b, mat, blendMode) - -proc draw*(a, b: Mask, pos = vec2(0, 0), blendMode = bmMask) {.inline.} = - ## Draws a mask onto a mask using a position offset with color blending. - a.draw(b, translate(pos), blendMode) - -proc draw*(mask: Mask, image: Image, mat: Mat3, blendMode = bmMask) {.inline.} = - ## Draws a image onto a mask using a matrix with color blending. - mask.drawUber(image, mat, blendMode) + when type(transform) is Vec2: + a.drawUber(b, translate(transform), blendMode) + else: + a.drawUber(b, transform, blendMode) proc draw*( - mask: Mask, image: Image, pos = vec2(0, 0), blendMode = bmMask + image: Image, mask: Mask, transform: Vec2 | Mat3 = vec2(), blendMode = bmMask ) {.inline.} = - ## Draws a image onto a mask using a position offset with color blending. - mask.draw(image, translate(pos), blendMode) + ## Draws a mask onto an image using a matrix with color blending. + when type(transform) is Vec2: + image.drawUber(mask, translate(transform), blendMode) + else: + image.drawUber(mask, transform, blendMode) + +proc draw*( + mask: Mask, image: Image, transform: Vec2 | Mat3 = vec2(), blendMode = bmMask +) {.inline.} = + ## Draws a image onto a mask using a matrix with color blending. + when type(transform) is Vec2: + mask.drawUber(image, translate(transform), blendMode) + else: + mask.drawUber(image, transform, blendMode) proc drawTiled*(dest, src: Image, mat: Mat3, blendMode = bmNormal) = dest.drawCorrect(src, mat, true, blendMode) diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim index d21b0be..763718c 100644 --- a/src/pixie/masks.nim +++ b/src/pixie/masks.nim @@ -73,17 +73,80 @@ proc minifyBy2*(mask: Mask, power = 1): Mask = if power == 0: return mask.copy() + var src = mask for i in 1 .. power: result = newMask(mask.width div 2, mask.height div 2) for y in 0 ..< result.height: - for x in 0 ..< result.width: + var x: int + when defined(amd64) and not defined(pixieNoSimd): + let + oddMask = mm_set1_epi16(cast[int16](0xff00)) + first8 = cast[M128i]([uint8.high, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + for _ in countup(0, result.width - 16, 8): + 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(mm_and_si128(top, oddMask), 8) + btmEven = mm_andnot_si128(oddMask, btm) + btmOdd = mm_srli_epi16(mm_and_si128(btm, oddMask), 8) + + topShiftedEven = mm_andnot_si128(oddMask, topShifted) + topShiftedOdd = mm_srli_epi16(mm_and_si128(topShifted, oddMask), 8) + btmShiftedEven = mm_andnot_si128(oddMask, btmShifted) + btmShiftedOdd = mm_srli_epi16(mm_and_si128(btmShifted, oddMask), 8) + + topAddedEven = mm_add_epi16(topEven, topShiftedEven) + btmAddedEven = mm_add_epi16(btmEven, btmShiftedEven) + topAddedOdd = mm_add_epi16(topOdd, topShiftedOdd) + bottomAddedOdd = mm_add_epi16(btmOdd, btmShiftedOdd) + + addedEven = mm_add_epi16(topAddedEven, btmAddedEven) + addedOdd = mm_add_epi16(topAddedOdd, bottomAddedOdd) + + 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 + + a = mm_and_si128(merged, first8) + b = mm_and_si128(mm_srli_si128(merged, 2), first8) + c = mm_and_si128(mm_srli_si128(merged, 4), first8) + d = mm_and_si128(mm_srli_si128(merged, 6), first8) + e = mm_and_si128(mm_srli_si128(merged, 8), first8) + f = mm_and_si128(mm_srli_si128(merged, 10), first8) + g = mm_and_si128(mm_srli_si128(merged, 12), first8) + h = mm_and_si128(mm_srli_si128(merged, 14), first8) + + ab = mm_or_si128(a, mm_slli_si128(b, 1)) + cd = mm_or_si128(c, mm_slli_si128(d, 1)) + ef = mm_or_si128(e, mm_slli_si128(f, 1)) + gh = mm_or_si128(g, mm_slli_si128(h, 1)) + + abcd = mm_or_si128(ab, mm_slli_si128(cd, 2)) + efgh = mm_or_si128(ef, mm_slli_si128(gh, 2)) + + abcdefgh = mm_or_si128(abcd, mm_slli_si128(efgh, 4)) + + mm_storeu_si128(result.data[result.dataIndex(x, y)].addr, abcdefgh) + x += 8 + + for x in x ..< result.width: let value = - mask.getValueUnsafe(x * 2 + 0, y * 2 + 0).uint32 + - mask.getValueUnsafe(x * 2 + 1, y * 2 + 0) + - mask.getValueUnsafe(x * 2 + 1, y * 2 + 1) + - mask.getValueUnsafe(x * 2 + 0, y * 2 + 1) + src.getValueUnsafe(x * 2 + 0, y * 2 + 0).uint32 + + src.getValueUnsafe(x * 2 + 1, y * 2 + 0) + + src.getValueUnsafe(x * 2 + 1, y * 2 + 1) + + src.getValueUnsafe(x * 2 + 0, y * 2 + 1) result.setValueUnsafe(x, y, (value div 4).uint8) + # Set src as this result for if we do another power + src = result + proc fillUnsafe*(data: var seq[uint8], value: uint8, start, len: int) = ## Fills the mask data with the parameter value starting at index start and ## continuing for len indices. diff --git a/tests/images/masks/maskMinified.png b/tests/images/masks/maskMinified.png index 7db6efe..1c10946 100644 Binary files a/tests/images/masks/maskMinified.png and b/tests/images/masks/maskMinified.png differ diff --git a/tests/images/masks/minifiedBlur.png b/tests/images/masks/minifiedBlur.png new file mode 100644 index 0000000..047ddca Binary files /dev/null and b/tests/images/masks/minifiedBlur.png differ diff --git a/tests/test_masks.nim b/tests/test_masks.nim index e936e13..c4f7047 100644 --- a/tests/test_masks.nim +++ b/tests/test_masks.nim @@ -13,27 +13,28 @@ block: 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 -# var path: Path -# path.moveTo(x + r, y) +block: + let + mask = newMask(100, 100) + r = 10.0 + x = 10.0 + y = 10.0 + h = 80.0 + w = 80.0 + var path: Path + 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) + path.roundedRect(x, y, w, h, r, r, r, r) + mask.fillPath(path) -# let minified = mask.minifyBy2() + let minified = mask.minifyBy2() -# doAssert minified.width == 50 and minified.height == 50 + doAssert minified.width == 50 and minified.height == 50 -# writeFile("tests/images/masks/maskMinified.png", minified.encodePng()) + writeFile("tests/images/masks/maskMinified.png", minified.encodePng()) block: let image = newImage(100, 100) @@ -155,3 +156,11 @@ block: mask.fillRect(rect(25, 25, 50, 50)) mask.blur(20) writeFile("tests/images/maskblur20.png", mask.encodePng()) + +block: + let mask = newMask(200, 200) + mask.fillRect(rect(25, 25, 150, 150)) + mask.blur(25) + + let minified = mask.minifyBy2() + writeFile("tests/images/masks/minifiedBlur.png", minified.encodePng())