diff --git a/experiments/bench_cairo_draw.nim b/experiments/bench_cairo_draw.nim index 8d8de7e..e6431f8 100644 --- a/experiments/bench_cairo_draw.nim +++ b/experiments/bench_cairo_draw.nim @@ -1,39 +1,4 @@ -import benchy, cairo, pixie, pixie/blends, pixie/internal - -when defined(amd64) and not defined(pixieNoSimd): - import nimsimd/sse2 - -when defined(release): - {.push checks: off.} - -proc drawBasic(backdrop, source: Image) = - for y in 0 ..< min(backdrop.height, source.height): - if isOpaque(source.data, source.dataIndex(0, y), source.width): - copyMem( - backdrop.data[backdrop.dataIndex(0, y)].addr, - source.data[source.dataIndex(0, y)].addr, - min(backdrop.width, source.width) * 4 - ) - else: - var x: int - when defined(amd64) and not defined(pixieNoSimd): - let vec255 = mm_set1_epi32(cast[int32](uint32.high)) - for _ in 0 ..< min(backdrop.width, source.width) div 4: - let sourceVec = mm_loadu_si128(source.data[source.dataIndex(x, y)].addr) - if mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, mm_setzero_si128())) != 0xffff: - if (mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) and 0x8888) == 0x8888: - mm_storeu_si128(backdrop.data[backdrop.dataIndex(x, y)].addr, sourceVec) - else: - let backdropVec = mm_loadu_si128(backdrop.data[backdrop.dataIndex(x, y)].addr) - mm_storeu_si128( - backdrop.data[backdrop.dataIndex(x, y)].addr, - blendNormalInlineSimd(backdropVec, sourceVec) - ) - x += 4 - # No scalar for now - -when defined(release): - {.pop.} +import benchy, cairo, pixie block: let @@ -43,12 +8,6 @@ block: ctx = tmp.create() timeIt "cairo draw normal": - # ctx.setSourceRgba(0.5, 0.5, 0.5, 1) - # let operator = ctx.getOperator() - # ctx.setOperator(OperatorSource) - # ctx.paint() - # ctx.setOperator(operator) - ctx.setSource(backdrop, 0, 0) ctx.paint() ctx.setSource(source, 0, 0) @@ -64,7 +23,6 @@ block: tmp = newImage(1568, 940) timeIt "pixie draw normal": - # tmp.fill(rgbx(127, 127, 127, 255)) tmp.draw(backdrop) tmp.draw(source) @@ -77,25 +35,11 @@ block: tmp = newImage(1568, 940) timeIt "pixie draw overwrite": - # tmp.fill(rgbx(127, 127, 127, 255)) tmp.draw(backdrop, blendMode = OverwriteBlend) tmp.draw(source) # tmp.writeFile("tmp2.png") -block: - let - backdrop = readImage("tests/fileformats/svg/masters/dragon2.png") - source = readImage("tests/fileformats/svg/masters/Ghostscript_Tiger.png") - tmp = newImage(1568, 940) - - timeIt "pixie draw basic": - # tmp.fill(rgbx(127, 127, 127, 255)) - tmp.drawBasic(backdrop) - tmp.drawBasic(source) - - # tmp.writeFile("tmp2.png") - block: let backdrop = imageSurfaceCreateFromPng("tests/fileformats/svg/masters/dragon2.png") @@ -104,17 +48,11 @@ block: ctx = tmp.create() timeIt "cairo draw mask": - ctx.setSourceRgba(1, 1, 1, 1) - let operator = ctx.getOperator() - ctx.setOperator(OperatorSource) - ctx.paint() - ctx.setOperator(operator) - ctx.setSource(backdrop, 0, 0) ctx.mask(source, 0, 0) tmp.flush() - # echo tmp.writeToPng("tmp_masked.png") + # tmp.writeToPng("tmp_masked.png") block: let diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 484e9b3..ef473f1 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -1,6 +1,6 @@ ## Blending modes. -import chroma, common, simd, std/math +import chroma, common, std/math # See https://www.w3.org/TR/compositing-1/ # See https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt @@ -412,82 +412,5 @@ proc blender*(blendMode: BlendMode): Blender {.raises: [].} = of SubtractMaskBlend: subtractMaskBlender of ExcludeMaskBlend: excludeMaskBlender -when defined(amd64) and allowSimd: - type - BlenderSimd* = proc(blackdrop, source: M128i): M128i {.gcsafe, raises: [].} - ## Function signature returned by blenderSimd. - - proc blendNormalSimd*(backdrop, source: M128i): M128i {.inline.} = - let - alphaMask = mm_set1_epi32(cast[int32](0xff000000)) - oddMask = mm_set1_epi16(cast[int16](0xff00)) - div255 = mm_set1_epi16(cast[int16](0x8081)) - - var - sourceAlpha = mm_and_si128(source, alphaMask) - backdropEven = mm_slli_epi16(backdrop, 8) - backdropOdd = mm_and_si128(backdrop, oddMask) - - sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) - - let k = mm_sub_epi32( - mm_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])), - sourceAlpha - ) - - backdropEven = mm_mulhi_epu16(backdropEven, k) - backdropOdd = mm_mulhi_epu16(backdropOdd, k) - - backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7) - backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7) - - mm_add_epi8( - source, - mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) - ) - - proc blendMaskSimd*(backdrop, source: M128i): M128i {.inline.} = - let - alphaMask = mm_set1_epi32(cast[int32](0xff000000)) - oddMask = mm_set1_epi16(cast[int16](0xff00)) - div255 = mm_set1_epi16(cast[int16](0x8081)) - - var - sourceAlpha = mm_and_si128(source, alphaMask) - backdropEven = mm_slli_epi16(backdrop, 8) - backdropOdd = mm_and_si128(backdrop, oddMask) - - sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) - - backdropEven = mm_mulhi_epu16(backdropEven, sourceAlpha) - backdropOdd = mm_mulhi_epu16(backdropOdd, sourceAlpha) - - 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 normalSimdBlender(backdrop, source: M128i): M128i = - blendNormalSimd(backdrop, source) - - proc maskSimdBlender(backdrop, source: M128i): M128i = - blendMaskSimd(backdrop, source) - - proc overwriteSimdBlender(backdrop, source: M128i): M128i = - source - - proc blenderSimd*(blendMode: BlendMode): BlenderSimd {.raises: [PixieError].} = - ## Returns a blend function for a given blend mode with SIMD support. - case blendMode: - of NormalBlend: normalSimdBlender - of MaskBlend: maskSimdBlender - of OverwriteBlend: overwriteSimdBlender - else: - raise newException(PixieError, "No SIMD blender for " & $blendMode) - - proc hasSimdBlender*(blendMode: BlendMode): bool {.inline, raises: [].} = - ## Is there a blend function for a given blend mode with SIMD support? - blendMode in {NormalBlend, MaskBlend, OverwriteBlend} - when defined(release): {.pop.} diff --git a/src/pixie/images.nim b/src/pixie/images.nim index bcce99b..ddc1153 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -394,63 +394,11 @@ proc getRgbaSmooth*( else: topMix -proc blitLine(a, b: ptr UncheckedArray[ColorRGBX], len: int, blender: Blender) = - for i in 0 ..< len: - a[i] = blender(a[i], b[i]) - -proc blitLineNormal(a, b: ptr UncheckedArray[ColorRGBX], len: int) {.hasSimd.} = - for i in 0 ..< len: - a[i] = blendNormal(a[i], b[i]) - -proc blitLineOverwrite(a, b: ptr UncheckedArray[ColorRGBX], len: int) = - copyMem(a[0].addr, b[0].addr, len * 4) - -template getUncheckedArray(a: Image, x, y: int): ptr UncheckedArray[ColorRGBX] = - cast[ptr UncheckedArray[ColorRGBX]](a.data[a.dataIndex(x, y)].addr) - -proc blitRect( - a, b: Image, pos = ivec2(0, 0), blendMode = NormalBlend -) = - ## Blits one image onto another using integer position with color blending. - let - px = pos.x.int - py = pos.y.int - xStart = max(-px, 0) - yStart = max(-py, 0) - xEnd = min(b.width, a.width - px) - yEnd = min(b.height, a.height - py) - - case blendMode: - of NormalBlend: - for y in yStart ..< yEnd: - blitLineNormal( - a.getUncheckedArray(xStart + px, y + py), - b.getUncheckedArray(xStart, y), - xEnd - xStart - ) - of OverwriteBlend: - {.linearScanEnd.} - for y in yStart ..< yEnd: - blitLineOverwrite( - a.getUncheckedArray(xStart + px, y + py), - b.getUncheckedArray(xStart, y), - xEnd - xStart - ) - else: - let blender = blendMode.blender() - for y in yStart ..< yEnd: - blitLine( - a.getUncheckedArray(xStart + px, y + py), - b.getUncheckedArray(xStart, y), - xEnd - xStart, - blender - ) - proc drawCorrect( - a, b: Image, transform = mat3(), blendMode = NormalBlend, tiled = false -) {.raises: [PixieError].} = - ## Draws one image onto another using matrix with color blending. - + a, b: Image, transform = mat3(), blendMode: BlendMode, tiled: bool +) = + ## Draws one image onto another using a matrix transform and color blending. + ## This proc is not about performance, it should be as simple as possible. var inverseTransform = transform.inverse() # Compute movement vectors @@ -466,7 +414,131 @@ proc drawCorrect( dx /= 2 dy /= 2 filterBy2 /= 2 - inverseTransform = scale(vec2(1/2, 1/2)) * inverseTransform + inverseTransform = scale(vec2(0.5, 0.5)) * inverseTransform + + while filterBy2 <= 0.5: + b = b.magnifyBy2() + p *= 2 + dx *= 2 + dy *= 2 + filterBy2 *= 2 + inverseTransform = scale(vec2(2, 2)) * inverseTransform + + 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 + backdrop = a.unsafe[x, y] + sample = b.getRgbaSmooth(xFloat, yFloat, tiled) + blended = blender(backdrop, sample) + a.unsafe[x, y] = blended + +template getUncheckedArray( + image: Image, x, y: int +): ptr UncheckedArray[ColorRGBX] = + cast[ptr UncheckedArray[ColorRGBX]](image.data[image.dataIndex(x, y)].addr) + +proc blitLine(a, b: ptr UncheckedArray[ColorRGBX], len: int, blender: Blender) {.inline.} = + for i in 0 ..< len: + a[i] = blender(a[i], b[i]) + +proc blitLineOverwrite(a, b: ptr UncheckedArray[ColorRGBX], len: int) {.inline.} = + copyMem(a[0].addr, b[0].addr, len * 4) + +proc blitLineNormal(a, b: ptr UncheckedArray[ColorRGBX], len: int) {.hasSimd.} = + for i in 0 ..< len: + a[i] = blendNormal(a[i], b[i]) + +proc blitLineMask(a, b: ptr UncheckedArray[ColorRGBX], len: int) {.hasSimd.} = + for i in 0 ..< len: + a[i] = blendMask(a[i], b[i]) + +proc blitRect(a, b: Image, pos: Ivec2, blendMode: BlendMode) = + let + px = pos.x.int + py = pos.y.int + + if px >= a.width or px + b.width <= 0 or py >= a.height or py + b.height <= 0: + if blendMode == MaskBlend: + a.fill(rgbx(0, 0, 0, 0)) + return + + let + xStart = max(-px, 0) + yStart = max(-py, 0) + xEnd = min(b.width, a.width - px) + yEnd = min(b.height, a.height - py) + + case blendMode: + of NormalBlend: + for y in yStart ..< yEnd: + blitLineNormal( + a.getUncheckedArray(xStart + px, y + py), + b.getUncheckedArray(xStart, y), + xEnd - xStart + ) + of OverwriteBlend: + for y in yStart ..< yEnd: + blitLineOverwrite( + a.getUncheckedArray(xStart + px, y + py), + b.getUncheckedArray(xStart, y), + xEnd - xStart + ) + of MaskBlend: + {.linearScanEnd.} + if yStart + py > 0: + zeroMem(a.data[0].addr, (yStart + py) * a.width * 4) + for y in yStart ..< yEnd: + if xStart + px > 0: + zeroMem(a.data[a.dataIndex(0, y + py)].addr, (xStart + px) * 4) + blitLineMask( + a.getUncheckedArray(xStart + px, y + py), + b.getUncheckedArray(xStart, y), + xEnd - xStart + ) + if xEnd + px < a.width: + zeroMem( + a.data[a.dataIndex(xEnd + px, y + py)].addr, + (a.width - (xEnd + px)) * 4 + ) + if yEnd + py < a.height: + zeroMem( + a.data[a.dataIndex(0, yEnd + py)].addr, + (a.height - (yEnd + py)) * a.width * 4 + ) + else: + let blender = blendMode.blender() + for y in yStart ..< yEnd: + blitLine( + a.getUncheckedArray(xStart + px, y + py), + b.getUncheckedArray(xStart, y), + xEnd - xStart, + blender + ) + +proc draw*( + a, b: Image, transform = mat3(), blendMode = NormalBlend +) {.raises: [PixieError].} = + ## Draws one image onto another using a matrix transform and color blending. + var + inverseTransform = transform.inverse() + # Compute movement vectors + p = inverseTransform * vec2(0 + h, 0 + h) + dx = inverseTransform * vec2(1 + h, 0 + h) - p + dy = inverseTransform * vec2(0 + h, 1 + h) - p + filterBy2 = max(dx.length, dy.length) + b = b + + while filterBy2 >= 2.0: + b = b.minifyBy2() + p /= 2 + dx /= 2 + dy /= 2 + filterBy2 /= 2 + inverseTransform = scale(vec2(0.5, 0.5)) * inverseTransform while filterBy2 <= 0.5: b = b.magnifyBy2() @@ -485,277 +557,10 @@ proc drawCorrect( transform[2, 1].fractional == 0.0 ) - 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 - 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, transform = mat3(), blendMode: BlendMode -) {.raises: [PixieError].} = - let - corners = [ - transform * vec2(0, 0), - transform * vec2(b.width.float32, 0), - transform * vec2(b.width.float32, b.height.float32), - transform * vec2(0, b.height.float32) - ] - perimeter = [ - segment(corners[0], corners[1]), - segment(corners[1], corners[2]), - segment(corners[2], corners[3]), - segment(corners[3], corners[0]) - ] - - var - inverseTransform = transform.inverse() - # Compute movement vectors - p = inverseTransform * vec2(0 + h, 0 + h) - dx = inverseTransform * vec2(1 + h, 0 + h) - p - dy = inverseTransform * vec2(0 + h, 1 + h) - p - filterBy2 = max(dx.length, dy.length) - b = b - - while filterBy2 >= 2.0: - b = b.minifyBy2() - p /= 2 - dx /= 2 - dy /= 2 - filterBy2 /= 2 - - while filterBy2 <= 0.5: - b = b.magnifyBy2() - p *= 2 - dx *= 2 - dy *= 2 - filterBy2 *= 2 - - let - hasRotationOrScaling = not(dx == vec2(1, 0) and dy == vec2(0, 1)) - smooth = not( - dx.length == 1.0 and - dy.length == 1.0 and - transform[2, 0].fractional == 0.0 and - transform[2, 1].fractional == 0.0 - ) - - # Determine where we should start and stop drawing in the y dimension - var - yMin = a.height - yMax = 0 - for segment in perimeter: - yMin = min(yMin, segment.at.y.floor.int) - yMax = max(yMax, segment.at.y.ceil.int) - yMin = yMin.clamp(0, a.height) - yMax = yMax.clamp(0, a.height) - - let blender = blendMode.blender() - - if blendMode == MaskBlend: - if yMin > 0: - zeroMem(a.data[0].addr, 4 * yMin * a.width) - - for y in yMin ..< yMax: - # Determine where we should start and stop drawing in the x dimension - var - xMin = a.width.float32 - xMax = 0.float32 - for yOffset in [0.float32, 1]: - let scanLine = Line( - a: vec2(-1000, y.float32 + yOffset), - b: vec2(1000, y.float32 + yOffset) - ) - for segment in perimeter: - var at: Vec2 - if scanline.intersects(segment, at) and segment.to != at: - xMin = min(xMin, at.x) - xMax = max(xMax, at.x) - - var xStart, xStop: int - if hasRotationOrScaling or smooth: - xStart = xMin.floor.int - xStop = xMax.ceil.int - else: - # Rotation of 360 degrees can cause knife-edge issues with floor and ceil - xStart = xMin.round().int - xStop = xMax.round().int - xStart = xStart.clamp(0, a.width) - xStop = xStop.clamp(0, a.width) - - # Skip this row if there is nothing in-bounds to draw - if xStart == a.width or xStop == 0: - continue - - if blendMode == MaskBlend: - if xStart > 0: - zeroMem(a.data[a.dataIndex(0, y)].addr, 4 * xStart) - - if smooth: - var srcPos = p + dx * xStart.float32 + dy * y.float32 - srcPos = vec2(srcPos.x - h, srcPos.y - h) - for x in xStart ..< xStop: - 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: - var x = xStart - if not hasRotationOrScaling: - let - srcPos = p + dx * x.float32 + dy * y.float32 - sy = srcPos.y.int - var sx = srcPos.x.int - - 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: - 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: - 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 - 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: - 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: - when type(a) is Image: - if blendMode.hasSimdBlender(): - let blenderSimd = blendMode.blenderSimd() - for _ in 0 ..< (xStop - xStart) div 16: - 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) - ) - x += 16 - sx += 16 - - var srcPos = p + dx * x.float32 + dy * y.float32 - srcPos = vec2( - clamp(srcPos.x, 0, b.width.float32), - clamp(srcPos.y, 0, b.height.float32) - ) - - case blendMode: - of OverwriteBlend: - for x in x ..< xStop: - 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) - 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) - 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) - 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: - if a.width - xStop > 0: - zeroMem(a.data[a.dataIndex(xStop, y)].addr, 4 * (a.width - xStop)) - - if blendMode == MaskBlend: - if a.height - yMax > 0: - zeroMem(a.data[a.dataIndex(0, yMax)].addr, 4 * a.width * (a.height - yMax)) - -proc draw*( - a, b: Image, transform = mat3(), blendMode = NormalBlend -) {.inline, raises: [PixieError].} = - ## Draws one image onto another using matrix with color blending. - a.drawUber(b, transform, blendMode) + if hasRotationOrScaling or smooth: + a.drawCorrect(b, inverseTransform.inverse(), blendMode, false) + else: + a.blitRect(b, ivec2(transform[2, 0].int32, transform[2, 1].int32), blendMode) proc drawTiled*( dst, src: Image, mat: Mat3, blendMode = NormalBlend diff --git a/src/pixie/simd/avx2.nim b/src/pixie/simd/avx2.nim index db06db8..7bd9c9c 100644 --- a/src/pixie/simd/avx2.nim +++ b/src/pixie/simd/avx2.nim @@ -1,4 +1,4 @@ -import avx, chroma, internal, nimsimd/avx2, pixie/common, vmath +import avx, chroma, internal, nimsimd/avx2, pixie/blends, pixie/common, vmath when defined(gcc) or defined(clang): {.localPassc: "-mavx2".} @@ -107,6 +107,10 @@ proc toPremultipliedAlphaAvx2*(data: var seq[ColorRGBA | ColorRGBX]) {.simd.} = let alphaMask = mm256_set1_epi32(cast[int32](0xff000000)) + shuffleControl = mm256_set_epi8( + 15, -1, 15, -1, 11, -1, 11, -1, 7, -1, 7, -1, 3, -1, 3, -1, + 15, -1, 15, -1, 11, -1, 11, -1, 7, -1, 7, -1, 3, -1, 3, -1 + ) oddMask = mm256_set1_epi16(0xff00) vec128 = mm256_set1_epi16(128) hiMask = mm256_set1_epi16(255 shl 8) @@ -118,7 +122,7 @@ proc toPremultipliedAlphaAvx2*(data: var seq[ColorRGBA | ColorRGBX]) {.simd.} = eq = mm256_cmpeq_epi8(values, alphaMask) if (mm256_movemask_epi8(eq) and 0x88888888) != 0x88888888: let - evenMultiplier = mm256_or_si256(alpha, mm256_srli_epi32(alpha, 16)) + evenMultiplier = mm256_shuffle_epi8(alpha, shuffleControl) oddMultiplier = mm256_or_si256(evenMultiplier, alphaMask) var colorsEven = mm256_slli_epi16(values, 8) @@ -376,5 +380,100 @@ proc minifyBy2Avx2*(image: Image, power = 1): Image {.simd.} = # Set src as this result for if we do another power src = result +proc blitLineNormalAvx2*( + a, b: ptr UncheckedArray[ColorRGBX], len: int +) {.simd.} = + let + alphaMask = mm256_set1_epi32(cast[int32](0xff000000)) + oddMask = mm256_set1_epi16(cast[int16](0xff00)) + div255 = mm256_set1_epi16(cast[int16](0x8081)) + vec255 = mm256_set1_epi8(255) + vecAlpha255 = mm256_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])) + shuffleControl = mm256_set_epi8( + 15, -1, 15, -1, 11, -1, 11, -1, 7, -1, 7, -1, 3, -1, 3, -1, + 15, -1, 15, -1, 11, -1, 11, -1, 7, -1, 7, -1, 3, -1, 3, -1 + ) + + var i: int + while i < len - 8: + let + source = mm256_loadu_si256(b[i].addr) + eq255 = mm256_cmpeq_epi8(source, vec255) + if (mm256_movemask_epi8(eq255) and 0x88888888) == 0x88888888: # Opaque source + mm256_storeu_si256(a[i].addr, source) + else: + let backdrop = mm256_loadu_si256(a[i].addr) + + var + sourceAlpha = mm256_and_si256(source, alphaMask) + backdropEven = mm256_slli_epi16(backdrop, 8) + backdropOdd = mm256_and_si256(backdrop, oddMask) + + sourceAlpha = mm256_shuffle_epi8(sourceAlpha, shuffleControl) + + let multiplier = mm256_sub_epi32(vecAlpha255, sourceAlpha) + + backdropEven = mm256_mulhi_epu16(backdropEven, multiplier) + backdropOdd = mm256_mulhi_epu16(backdropOdd, multiplier) + backdropEven = mm256_srli_epi16(mm256_mulhi_epu16(backdropEven, div255), 7) + backdropOdd = mm256_srli_epi16(mm256_mulhi_epu16(backdropOdd, div255), 7) + + let added = mm256_add_epi8( + source, + mm256_or_si256(backdropEven, mm256_slli_epi16(backdropOdd, 8)) + ) + + mm256_storeu_si256(a[i].addr, added) + + i += 8 + + for i in i ..< len: + a[i] = blendNormal(a[i], b[i]) + +proc blitLineMaskAvx2*( + a, b: ptr UncheckedArray[ColorRGBX], len: int +) {.simd.} = + let + alphaMask = mm256_set1_epi32(cast[int32](0xff000000)) + oddMask = mm256_set1_epi16(cast[int16](0xff00)) + div255 = mm256_set1_epi16(cast[int16](0x8081)) + vec255 = mm256_set1_epi8(255) + shuffleControl = mm256_set_epi8( + 15, -1, 15, -1, 11, -1, 11, -1, 7, -1, 7, -1, 3, -1, 3, -1, + 15, -1, 15, -1, 11, -1, 11, -1, 7, -1, 7, -1, 3, -1, 3, -1 + ) + + var i: int + while i < len - 8: + let + source = mm256_loadu_si256(b[i].addr) + eq255 = mm256_cmpeq_epi8(source, vec255) + if (mm256_movemask_epi8(eq255) and 0x88888888) == 0x88888888: # Opaque source + discard + else: + let backdrop = mm256_loadu_si256(a[i].addr) + + var + sourceAlpha = mm256_and_si256(source, alphaMask) + backdropEven = mm256_slli_epi16(backdrop, 8) + backdropOdd = mm256_and_si256(backdrop, oddMask) + + sourceAlpha = mm256_shuffle_epi8(sourceAlpha, shuffleControl) + + backdropEven = mm256_mulhi_epu16(backdropEven, sourceAlpha) + backdropOdd = mm256_mulhi_epu16(backdropOdd, sourceAlpha) + backdropEven = mm256_srli_epi16(mm256_mulhi_epu16(backdropEven, div255), 7) + backdropOdd = mm256_srli_epi16(mm256_mulhi_epu16(backdropOdd, div255), 7) + + mm256_storeu_si256( + a[i].addr, + mm256_or_si256(backdropEven, mm256_slli_epi16(backdropOdd, 8)) + ) + + i += 8 + + for i in i ..< len: + a[i] = blendMask(a[i], b[i]) + when defined(release): {.pop.} diff --git a/src/pixie/simd/sse2.nim b/src/pixie/simd/sse2.nim index 062b972..c8e0dc8 100644 --- a/src/pixie/simd/sse2.nim +++ b/src/pixie/simd/sse2.nim @@ -1,4 +1,4 @@ -import chroma, internal, nimsimd/sse2, pixie/common, vmath +import chroma, internal, nimsimd/sse2, pixie/blends, pixie/common, vmath when defined(release): {.push checks: off.} @@ -15,6 +15,56 @@ proc unpackAlphaValues*(v: M128i): M128i {.inline, raises: [].} = result = mm_unpacklo_epi8(mm_setzero_si128(), v) result = mm_unpacklo_epi8(mm_setzero_si128(), result) +proc blendNormalSimd*(backdrop, source: M128i): M128i {.inline.} = + let + alphaMask = mm_set1_epi32(cast[int32](0xff000000)) + oddMask = mm_set1_epi16(cast[int16](0xff00)) + div255 = mm_set1_epi16(cast[int16](0x8081)) + + var + sourceAlpha = mm_and_si128(source, alphaMask) + backdropEven = mm_slli_epi16(backdrop, 8) + backdropOdd = mm_and_si128(backdrop, oddMask) + + sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) + + let k = mm_sub_epi32( + mm_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])), + sourceAlpha + ) + + backdropEven = mm_mulhi_epu16(backdropEven, k) + backdropOdd = mm_mulhi_epu16(backdropOdd, k) + + backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7) + backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7) + + mm_add_epi8( + source, + mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) + ) + +proc blendMaskSimd*(backdrop, source: M128i): M128i {.inline.} = + let + alphaMask = mm_set1_epi32(cast[int32](0xff000000)) + oddMask = mm_set1_epi16(cast[int16](0xff00)) + div255 = mm_set1_epi16(cast[int16](0x8081)) + + var + sourceAlpha = mm_and_si128(source, alphaMask) + backdropEven = mm_slli_epi16(backdrop, 8) + backdropOdd = mm_and_si128(backdrop, oddMask) + + sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) + + backdropEven = mm_mulhi_epu16(backdropEven, sourceAlpha) + backdropOdd = mm_mulhi_epu16(backdropOdd, sourceAlpha) + + 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 fillUnsafeSse2*( data: var seq[ColorRGBX], color: SomeColor, @@ -480,49 +530,89 @@ proc magnifyBy2Sse2*(image: Image, power = 1): Image {.simd.} = proc blitLineNormalSse2*( a, b: ptr UncheckedArray[ColorRGBX], len: int ) {.simd.} = + let + alphaMask = mm_set1_epi32(cast[int32](0xff000000)) + oddMask = mm_set1_epi16(cast[int16](0xff00)) + div255 = mm_set1_epi16(cast[int16](0x8081)) + vec255 = mm_set1_epi8(255) + vecAlpha255 = mm_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])) - # TODO align to 16 - - var i = 0 + var i: int while i < len - 4: - let source = mm_loadu_si128(b[i].addr) - backdrop = mm_loadu_si128(a[i].addr) - alphaMask = mm_set1_epi32(cast[int32](0xff000000)) - oddMask = mm_set1_epi16(cast[int16](0xff00)) - div255 = mm_set1_epi16(cast[int16](0x8081)) + eq255 = mm_cmpeq_epi8(source, vec255) + if (mm_movemask_epi8(eq255) and 0x00008888) == 0x00008888: # Opaque source + mm_storeu_si128(a[i].addr, source) + else: + let backdrop = mm_loadu_si128(a[i].addr) - var - sourceAlpha = mm_and_si128(source, alphaMask) - backdropEven = mm_slli_epi16(backdrop, 8) - backdropOdd = mm_and_si128(backdrop, oddMask) + var + sourceAlpha = mm_and_si128(source, alphaMask) + backdropEven = mm_slli_epi16(backdrop, 8) + backdropOdd = mm_and_si128(backdrop, oddMask) - sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) + sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) - let k = mm_sub_epi32( - mm_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])), - sourceAlpha - ) + let multiplier = mm_sub_epi32(vecAlpha255, sourceAlpha) - backdropEven = mm_mulhi_epu16(backdropEven, k) - backdropOdd = mm_mulhi_epu16(backdropOdd, k) + backdropEven = mm_mulhi_epu16(backdropEven, multiplier) + backdropOdd = mm_mulhi_epu16(backdropOdd, multiplier) + backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7) + backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7) - backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7) - backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7) + let added = mm_add_epi8( + source, + mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) + ) - let done = mm_add_epi8( - source, - mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) - ) - - mm_storeu_si128(a[i].addr, done) + mm_storeu_si128(a[i].addr, added) i += 4 - # TODO last 1-3 pixels - # for i in i ..< len: - # a[i] = blendNormal(a[i], b[i]) + for i in i ..< len: + a[i] = blendNormal(a[i], b[i]) + +proc blitLineMaskSse2*( + a, b: ptr UncheckedArray[ColorRGBX], len: int +) {.simd.} = + let + alphaMask = mm_set1_epi32(cast[int32](0xff000000)) + oddMask = mm_set1_epi16(cast[int16](0xff00)) + div255 = mm_set1_epi16(cast[int16](0x8081)) + vec255 = mm_set1_epi8(255) + + var i: int + while i < len - 4: + let + source = mm_loadu_si128(b[i].addr) + eq255 = mm_cmpeq_epi8(source, vec255) + if (mm_movemask_epi8(eq255) and 0x00008888) == 0x00008888: # Opaque source + discard + else: + let backdrop = mm_loadu_si128(a[i].addr) + + var + sourceAlpha = mm_and_si128(source, alphaMask) + backdropEven = mm_slli_epi16(backdrop, 8) + backdropOdd = mm_and_si128(backdrop, oddMask) + + sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16)) + + backdropEven = mm_mulhi_epu16(backdropEven, sourceAlpha) + backdropOdd = mm_mulhi_epu16(backdropOdd, sourceAlpha) + backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7) + backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7) + + mm_storeu_si128( + a[i].addr, + mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) + ) + + i += 4 + + for i in i ..< len: + a[i] = blendMask(a[i], b[i]) when defined(release): {.pop.} diff --git a/tests/bench_blends.nim b/tests/bench_blends.nim index 59befb7..2826743 100644 --- a/tests/bench_blends.nim +++ b/tests/bench_blends.nim @@ -133,17 +133,3 @@ reset() timeIt "blendExcludeMask": for i in 0 ..< backdrop.data.len: backdrop.data[i] = blendExcludeMask(backdrop.data[i], source.data[i]) - -when defined(amd64) and not defined(pixieNoSimd): - import nimsimd/sse2 - - reset() - - timeIt "blendNormal [simd]": - var i: int - while i < backdrop.data.len - 4: - let - b = mm_loadu_si128(backdrop.data[i].addr) - s = mm_loadu_si128(source.data[i].addr) - mm_storeu_si128(backdrop.data[i].addr, blendNormalSimd(b, s)) - i += 4 diff --git a/tests/bench_images.nim b/tests/bench_images.nim index 423cd57..447ac0b 100644 --- a/tests/bench_images.nim +++ b/tests/bench_images.nim @@ -32,12 +32,12 @@ timeIt "isOpaque": reset() timeIt "subImage": - keep image.subImage(0, 0, 256, 256) + discard image.subImage(0, 0, 256, 256) reset() timeIt "superImage": - keep image.superImage(-10, -10, 2580, 1460) + discard image.superImage(-10, -10, 2580, 1460) reset() diff --git a/tests/bench_images_draw.nim b/tests/bench_images_draw.nim index ed42281..ffd03c7 100644 --- a/tests/bench_images_draw.nim +++ b/tests/bench_images_draw.nim @@ -9,7 +9,26 @@ block: timeIt "big-on-bigger NormalBlend": a.draw(b, translate(vec2(25, 25)), NormalBlend) - keep(b) + +block: + let + a = newImage(1000, 1000) + b = newImage(500, 500) + a.fill(rgba(255, 0, 0, 255)) + b.fill(rgba(0, 255, 0, 255)) + + timeIt "big-on-bigger MaskBlend": + a.draw(b, translate(vec2(25, 25)), MaskBlend) + +block: + let + a = newImage(1000, 1000) + b = newImage(500, 500) + a.fill(rgba(255, 0, 0, 255)) + b.fill(rgba(0, 255, 0, 255)) + + timeIt "big-on-bigger OverwriteBlend": + a.draw(b, translate(vec2(25, 25)), OverwriteBlend) block: let @@ -20,7 +39,6 @@ block: timeIt "scale x0.5": a.draw(b, translate(vec2(25, 25)) * scale(vec2(0.5, 0.5)), NormalBlend) - keep(b) block: let @@ -31,7 +49,6 @@ block: timeIt "scale x2": a.draw(b, translate(vec2(25, 25)) * scale(vec2(2, 2)), NormalBlend) - keep(b) block: let @@ -42,7 +59,6 @@ block: timeIt "smooth x-translate": a.draw(b, translate(vec2(25.2, 0)), NormalBlend) - keep(b) block: let @@ -53,7 +69,6 @@ block: timeIt "smooth y-translate": a.draw(b, translate(vec2(0, 25.2)), NormalBlend) - keep(b) block: let @@ -64,7 +79,6 @@ block: timeIt "smooth translate": a.draw(b, translate(vec2(25.2, 25.2)), NormalBlend) - keep(b) block: let @@ -75,7 +89,6 @@ block: timeIt "smooth rotate 45": a.draw(b, translate(vec2(0, 500)) * rotate(toRadians(45)), NormalBlend) - keep(b) block: let @@ -85,14 +98,12 @@ block: timeIt "shadow no offset": b.fill(rgba(0, 0, 0, 255)) a.draw(b, translate(vec2(25, 25))) - - let shadow = a.shadow( + discard a.shadow( offset = vec2(0, 0), spread = 10, blur = 10, color = rgba(0, 0, 0, 255) ) - keep(shadow) block: let @@ -102,11 +113,9 @@ block: timeIt "shadow with offset": b.fill(rgba(0, 0, 0, 255)) a.draw(b, translate(vec2(25, 25))) - - let shadow = a.shadow( + discard a.shadow( offset = vec2(10, 10), spread = 10, blur = 10, color = rgba(0, 0, 0, 255) ) - keep(shadow) diff --git a/tests/bench_images_draw_correct.nim b/tests/bench_images_draw_correct.nim deleted file mode 100644 index 93a27a0..0000000 --- a/tests/bench_images_draw_correct.nim +++ /dev/null @@ -1,33 +0,0 @@ -import benchy, pixie, pixie/images {.all.}, strformat, xrays - -block: - let - a = newImage(1000, 1000) - b = newImage(500, 500) - a.fill(rgba(255, 0, 0, 255)) - b.fill(rgba(0, 255, 0, 255)) - - a.drawCorrect(b, translate(vec2(250, 250)), blendMode = OverwriteBlend) - a.writeFile("tests/images/rotate0.png") - -block: - let - a = newImage(1000, 1000) - b = newImage(500, 500) - - timeIt "drawCorrect": - a.fill(rgba(255, 0, 0, 255)) - b.fill(rgba(0, 255, 0, 255)) - - a.drawCorrect(b, translate(vec2(250, 250)), blendMode = OverwriteBlend) - -block: - let - a = newImage(1000, 1000) - b = newImage(500, 500) - - timeIt "draw": - a.fill(rgba(255, 0, 0, 255)) - b.fill(rgba(0, 255, 0, 255)) - - a.draw(b, translate(vec2(250, 250)), blendMode = OverwriteBlend) diff --git a/tests/bench_paths.nim b/tests/bench_paths.nim index 7f5a66f..8b3c8c0 100644 --- a/tests/bench_paths.nim +++ b/tests/bench_paths.nim @@ -3,7 +3,7 @@ import benchy, pixie let pathStr = "m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.564,0.133,0.737-0.001,2.021-1.565,4.024-2.468,6.46-3.05,0.124-0.029,0.398,0.438,0.767,0.277,1.613-0.703,3.623-0.645,4.807-1.983,3.767,0.224,7.332-0.892,10.723-2.2,1.161-0.448,2.431-1.007,3.632-1.509,1.376-0.576,2.58-1.504,3.692-2.645,0.133-0.136,0.487-0.046,0.754-0.046-0.04-0.863,0.922-0.99,1.169-1.612,0.092-0.232-0.058-0.628,0.075-0.73,2.138-1.63,3.058-3.648,1.889-6.025-0.285-0.578-0.534-1.196-1.1-1.672-1.085-0.911-2.187-0.057-3.234-0.361-0.159,0.628-0.888,0.456-1.274,0.654-0.859,0.439-2.192-0.146-3.051,0.292-1.362,0.695-2.603,0.864-4.025,1.241-0.312,0.082-1.09-0.014-1.25,0.613-0.134-0.134-0.282-0.368-0.388-0.346-1.908,0.396-3.168,0.61-4.469,2.302-0.103,0.133-0.545-0.046-0.704,0.089-0.957,0.808-1.362,2.042-2.463,2.714-0.201,0.123-0.553-0.045-0.747,0.084-0.646,0.431-1.013,1.072-1.655,1.519-0.329,0.229-0.729-0.096-0.697-0.352,0.245-1.947,0.898-3.734,0.323-5.61,2.077-2.52,4.594-4.469,6.4-7.2,0.015-2.166,0.707-4.312,0.594-6.389-0.01-0.193-0.298-0.926-0.424-1.273-0.312-0.854,0.594-1.92-0.25-2.644-1.404-1.203-2.696-0.327-3.52,1.106-1.838,0.39-3.904,1.083-5.482-0.151-1.007-0.787-1.585-1.693-2.384-2.749-0.985-1.302-0.65-2.738-0.58-4.302,0.006-0.128-0.309-0.264-0.309-0.398,0.001-0.135,0.221-0.266,0.355-0.4-0.706-0.626-0.981-1.684-2-2,0.305-1.092-0.371-1.976-1.242-2.278-1.995-0.691-3.672,1.221-5.564,1.294-0.514,0.019-0.981-1.019-1.63-1.344-0.432-0.216-1.136-0.249-1.498,0.017-0.688,0.504-1.277,0.618-2.035,0.823-1.617,0.436-2.895,1.53-4.375,2.385-1.485,0.857-2.44,2.294-3.52,3.614-0.941,1.152-1.077,3.566,0.343,4.066,1.843,0.65,3.147-2.053,5.113-1.727,0.312,0.051,0.518,0.362,0.408,0.75,0.389,0.109,0.607-0.12,0.8-0.4,0.858,1.019,2.022,1.356,2.96,2.229,0.97,0.904,2.716,0.486,3.731,1.483,1.529,1.502,0.97,4.183,2.909,5.488-0.586,1.313-1.193,2.59-1.528,4.017-0.282,1.206,0.712,2.403,1.923,2.312,1.258-0.094,1.52-0.853,2.005-1.929,0.267,0.267,0.736,0.564,0.695,0.78-0.457,2.387-1.484,4.38-1.942,6.811-0.059,0.317-0.364,0.519-0.753,0.409-0.468,4.149-4.52,6.543-7.065,9.708-0.403,0.502-0.407,1.751,0.002,2.154,1.403,1.387,3.363-0.159,5.063-0.662,0.213-1.206,1.072-2.148,2.404-2.092,0.256,0.01,0.491-0.532,0.815-0.662,0.348-0.138,0.85,0.086,1.136-0.112,1.729-1.195,3.137-2.301,4.875-3.49,0.192-0.131,0.536,0.028,0.752-0.08,0.325-0.162,0.512-0.549,0.835-0.734,0.348-0.2,0.59,0.09,0.783,0.37-0.646,0.349-0.65,1.306-1.232,1.508-0.775,0.268-1.336,0.781-2.01,1.228-0.292,0.193-0.951-0.055-1.055,0.124-0.598,1.028-1.782,1.466-2.492,2.349z" timeIt "parsePath": - keep parsePath(pathStr) + discard parsePath(pathStr) block: let path = parsePath(""" diff --git a/tests/bench_qoi.nim b/tests/bench_qoi.nim index 105addd..b80b29c 100644 --- a/tests/bench_qoi.nim +++ b/tests/bench_qoi.nim @@ -3,4 +3,4 @@ import benchy, pixie/fileformats/qoi let data = readFile("tests/fileformats/qoi/testcard_rgba.qoi") timeIt "pixie decode": - keep decodeQoi(data) + discard decodeQoi(data) diff --git a/tests/fuzz_image_draw.nim b/tests/fuzz_image_draw.nim index 0a80cd8..d8d43b8 100644 --- a/tests/fuzz_image_draw.nim +++ b/tests/fuzz_image_draw.nim @@ -2,30 +2,26 @@ import pixie, random randomize() -for i in 0 ..< 25: +for i in 0 ..< 250: let a = newImage(rand(1 .. 20), rand(1 .. 20)) for j in 0 ..< 25: let b = newImage(rand(1 .. 20), rand(1 .. 20)) - let - translation = vec2(rand(25.0), rand(25.0)) - vec2(5, 5) - rotation = rand(2 * PI).float32 + let translation = vec2(rand(-25..25).float32, rand(-25..25).float32) - echo a, " ", b, " ", translation, " ", rotation + echo a, " ", b, " ", translation a.draw(b, translate(vec2(translation.x.trunc, translation.y.trunc))) - a.draw(b, translate(translation) * rotate(rotation)) + a.draw(b, translate(translation)) -for i in 0 ..< 25: +for i in 0 ..< 250: let a = newImage(rand(1 .. 2000), rand(1 .. 2000)) for j in 0 ..< 25: let b = newImage(rand(1 .. 1000), rand(1 .. 1000)) - let - translation = vec2(rand(2500.0), rand(2500.0)) - vec2(500, 500) - rotation = rand(2 * PI).float32 + let translation = vec2(rand(-2500..2500).float32, rand(-2500..2500).float32) - echo a, " ", b, " ", translation, " ", rotation + echo a, " ", b, " ", translation a.draw(b, translate(vec2(translation.x.trunc, translation.y.trunc))) - a.draw(b, translate(translation) * rotate(rotation)) + a.draw(b, translate(translation)) diff --git a/tests/fuzz_image_draw_smooth.nim b/tests/fuzz_image_draw_smooth.nim new file mode 100644 index 0000000..0a80cd8 --- /dev/null +++ b/tests/fuzz_image_draw_smooth.nim @@ -0,0 +1,31 @@ +import pixie, random + +randomize() + +for i in 0 ..< 25: + let a = newImage(rand(1 .. 20), rand(1 .. 20)) + for j in 0 ..< 25: + let b = newImage(rand(1 .. 20), rand(1 .. 20)) + + let + translation = vec2(rand(25.0), rand(25.0)) - vec2(5, 5) + rotation = rand(2 * PI).float32 + + echo a, " ", b, " ", translation, " ", rotation + + a.draw(b, translate(vec2(translation.x.trunc, translation.y.trunc))) + a.draw(b, translate(translation) * rotate(rotation)) + +for i in 0 ..< 25: + let a = newImage(rand(1 .. 2000), rand(1 .. 2000)) + for j in 0 ..< 25: + let b = newImage(rand(1 .. 1000), rand(1 .. 1000)) + + let + translation = vec2(rand(2500.0), rand(2500.0)) - vec2(500, 500) + rotation = rand(2 * PI).float32 + + echo a, " ", b, " ", translation, " ", rotation + + a.draw(b, translate(vec2(translation.x.trunc, translation.y.trunc))) + a.draw(b, translate(translation) * rotate(rotation)) diff --git a/tests/images/maskClearsOnDraw0.png b/tests/images/maskClearsOnDraw0.png new file mode 100644 index 0000000..8654d56 Binary files /dev/null and b/tests/images/maskClearsOnDraw0.png differ diff --git a/tests/images/maskClearsOnDraw1.png b/tests/images/maskClearsOnDraw1.png new file mode 100644 index 0000000..108525e Binary files /dev/null and b/tests/images/maskClearsOnDraw1.png differ diff --git a/tests/images/maskClearsOnDraw2.png b/tests/images/maskClearsOnDraw2.png new file mode 100644 index 0000000..2d91cf9 Binary files /dev/null and b/tests/images/maskClearsOnDraw2.png differ diff --git a/tests/images/maskClearsOnDraw3.png b/tests/images/maskClearsOnDraw3.png new file mode 100644 index 0000000..c1b353a Binary files /dev/null and b/tests/images/maskClearsOnDraw3.png differ diff --git a/tests/images/maskClearsOnDraw4.png b/tests/images/maskClearsOnDraw4.png new file mode 100644 index 0000000..854f1a2 Binary files /dev/null and b/tests/images/maskClearsOnDraw4.png differ diff --git a/tests/test_images_draw.nim b/tests/test_images_draw.nim index d6f3546..e154d5c 100644 --- a/tests/test_images_draw.nim +++ b/tests/test_images_draw.nim @@ -307,3 +307,54 @@ block: a.draw(b, translate(translation)) a.xray("tests/images/fillOptimization2.png") + +block: + let transforms = [ + translate(vec2(0, 0)), + translate(vec2(50, -50)), + translate(vec2(50, 50)), + translate(vec2(-50, 50)), + translate(vec2(-50, -50)) + ] + + let path = newPath() + path.rect(10, 10, 80, 80) + + let mask = newImage(100, 100) + mask.fillPath(path, color(1, 1, 1, 1)) + + let a = newImage(100, 100) + + for i, transform in transforms: + a.fill(color(0, 0, 1, 1)) + a.draw(mask, transform, blendMode = MaskBlend) + a.xray("tests/images/maskClearsOnDraw" & $i & ".png") + +block: + let transforms = [ + translate(vec2(100, 100)), + translate(vec2(100, -100)), + translate(vec2(-100, 100)), + translate(vec2(-100, -100)), + translate(vec2(0, 1000)), + translate(vec2(0, -1000)), + translate(vec2(1000, 0)), + translate(vec2(-1000, 0)), + translate(vec2(1000, 1000)), + translate(vec2(1000, -1000)), + translate(vec2(-1000, 1000)), + translate(vec2(-1000, -1000)), + ] + + let path = newPath() + path.rect(10, 10, 80, 80) + + let mask = newImage(100, 100) + mask.fillPath(path, color(1, 1, 1, 1)) + + let a = newImage(100, 100) + + for i, transform in transforms: + a.fill(color(0, 0, 1, 1)) + a.draw(mask, transform, blendMode = MaskBlend) + doAssert a.isTransparent()