diff --git a/src/pixie/images.nim b/src/pixie/images.nim index b86fb1e..53b46bc 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -70,20 +70,16 @@ proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} = proc fill*(image: Image, rgba: ColorRgba) = ## Fills the image with a solid color. - for y in 0 ..< image.height: - for x in 0 ..< image.width: - image.setRgbaUnsafe(x, y, rgba) + for c in image.data.mitems: + c = rgba proc invert*(image: Image) = ## Inverts all of the colors and alpha. - for y in 0 ..< image.height: - for x in 0 ..< image.width: - var rgba = image.getRgbaUnsafe(x, y) - rgba.r = 255 - rgba.r - rgba.g = 255 - rgba.g - rgba.b = 255 - rgba.b - rgba.a = 255 - rgba.a - image.setRgbaUnsafe(x, y, rgba) + for rgba in image.data.mitems: + rgba.r = 255 - rgba.r + rgba.g = 255 - rgba.g + rgba.b = 255 - rgba.b + rgba.a = 255 - rgba.a proc subImage*(image: Image, x, y, w, h: int): Image = ## Gets a sub image of the main image. @@ -210,17 +206,17 @@ proc resize*(srcImage: Image, width, height: int): Image = proc blur*(image: Image, radius: float32): Image = ## Applies Gaussian blur to the image given a radius. - let radius = (radius).int + let radius = radius.int if radius == 0: return image.copy() # Compute lookup table for 1d Gaussian kernel. - var lookup = newSeq[float](radius*2+1) + var lookup = newSeq[float](radius * 2 + 1) var total = 0.0 for xb in -radius .. radius: let s = radius.float32 / 2.2 # 2.2 matches Figma. let x = xb.float32 - let a = 1/sqrt(2*PI*s^2) * exp(-1*x^2/(2*s^2)) + let a = 1 / sqrt(2 * PI * s^2) * exp(-1 * x^2 / (2 * s^2)) lookup[xb + radius] = a total += a for xb in -radius .. radius: @@ -270,17 +266,18 @@ proc blur*(image: Image, radius: float32): Image = proc blurAlpha*(image: Image, radius: float32): Image = ## Applies Gaussian blur to the image given a radius. - let radius = (radius).int + let radius = radius.int if radius == 0: return image.copy() # Compute lookup table for 1d Gaussian kernel. - var lookup = newSeq[float](radius*2+1) - var total = 0.0 + var + lookup = newSeq[float](radius * 2 + 1) + total = 0.0 for xb in -radius .. radius: let s = radius.float32 / 2.2 # 2.2 matches Figma. let x = xb.float32 - let a = 1/sqrt(2*PI*s^2) * exp(-1*x^2/(2*s^2)) + let a = 1 / sqrt(2 * PI * s^2) * exp(-1 * x^2 / (2 * s^2)) lookup[xb + radius] = a total += a for xb in -radius .. radius: @@ -295,7 +292,7 @@ proc blurAlpha*(image: Image, radius: float32): Image = let c2 = image[x + xb, y] let a = lookup[xb + radius] alpha += c2.a.float32 * a - blurX.setRgbaUnsafe(x, y, rgba(0,0,0, (alpha).uint8)) + blurX.setRgbaUnsafe(x, y, rgba(0, 0, 0, alpha.uint8)) # Blur in the Y direction. var blurY = newImage(image.width, image.height) @@ -306,7 +303,7 @@ proc blurAlpha*(image: Image, radius: float32): Image = let c2 = blurX[x, y + yb] let a = lookup[yb + radius] alpha += c2.a.float32 * a - blurY.setRgbaUnsafe(x, y, rgba(0,0,0, (alpha).uint8)) + blurY.setRgbaUnsafe(x, y, rgba(0, 0, 0, alpha.uint8)) return blurY @@ -351,27 +348,23 @@ proc shadow*( result.fill(color) result.draw(shadow, blendMode = bmMask) -proc applyOpacity*(image: Image, opacity: float32): Image = +proc applyOpacity*(image: Image, opacity: float32) = ## Multiplies alpha of the image by opacity. - result = newImage(image.width, image.height) let op = (255 * opacity).uint32 - for y in 0 ..< image.height: - for x in 0 ..< image.width: - var rgba = image.getRgbaUnsafe(x, y) - rgba.a = ((rgba.a.uint32 * op) div 255).clamp(0, 255).uint8 - result.setRgbaUnsafe(x, y, rgba) + for i in 0 ..< image.data.len: + var rgba = image.data[i] + rgba.a = ((rgba.a.uint32 * op) div 255).clamp(0, 255).uint8 + image.data[i] = rgba -proc sharpOpacity*(image: Image): Image = +proc sharpOpacity*(image: Image) = ## Sharpens the opacity to extreme. ## A = 0 stays 0. Anything else turns into 255. - result = newImage(image.width, image.height) - for y in 0 ..< image.height: - for x in 0 ..< image.width: - var rgba = image.getRgbaUnsafe(x, y) - if rgba.a == 0: - result.setRgbaUnsafe(x, y, rgba(0, 0, 0, 0)) - else: - result.setRgbaUnsafe(x, y, rgba(255, 255, 255, 255)) + for i in 0 ..< image.data.len: + var rgba = image.data[i] + if rgba.a == 0: + image.data[i] = rgba(0, 0, 0, 0) + else: + image.data[i] = rgba(255, 255, 255, 255) proc drawCorrect*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Image = ## Draws one image onto another using matrix with color blending. @@ -410,7 +403,6 @@ proc drawUberStatic( start, stepX, stepY: Vec2, lines: array[0..3, Segment], blendMode: static[BlendMode], - inPlace: static[bool], smooth: static[bool], ) = for y in 0 ..< a.height: @@ -440,12 +432,6 @@ proc drawUberStatic( when blendMode == bmIntersectMask: if xMin > 0: zeroMem(c.getAddr(0, y), 4*xMin) - else: - when not inPlace: - # for x in 0 ..< xMin: - # result.setRgbaUnsafe(x, y, a.getRgbaUnsafe(x, y)) - if xMin > 0: - copyMem(c.getAddr(0, y), a.getAddr(0, y), 4*xMin) for x in xMin ..< xMax: let srcPos = start + stepX * float32(x) + stepY * float32(y) @@ -464,12 +450,6 @@ proc drawUberStatic( when blendMode == bmIntersectMask: if a.width - xMax > 0: zeroMem(c.getAddr(xMax, y), 4*(a.width - xMax)) - else: - when not inPlace: - # for x in xMax ..< a.width: - # result.setRgbaUnsafe(x, y, a.getRgbaUnsafe(x, y)) - if a.width - xMax > 0: - copyMem(c.getAddr(xMax, y), a.getAddr(xMax, y), 4*(a.width - xMax)) proc draw*(a, b: Image, mat: Mat3, blendMode: BlendMode) = ## Draws one image onto another using matrix with color blending. @@ -511,54 +491,54 @@ proc draw*(a, b: Image, mat: Mat3, blendMode: BlendMode) = if not smooth: case blendMode - of bmNormal: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmNormal, true, false) - of bmDarken: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDarken, true, false) - of bmMultiply: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMultiply, true, false) - of bmLinearBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearBurn, true, false) - of bmColorBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorBurn, true, false) - of bmLighten: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLighten, true, false) - of bmScreen: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmScreen, true, false) - of bmLinearDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearDodge, true, false) - of bmColorDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorDodge, true, false) - of bmOverlay: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverlay, true, false) - of bmSoftLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSoftLight, true, false) - of bmHardLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHardLight, true, false) - of bmDifference: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDifference, true, false) - of bmExclusion: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExclusion, true, false) - of bmHue: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHue, true, false) - of bmSaturation: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSaturation, true, false) - of bmColor: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColor, true, false) - of bmLuminosity: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLuminosity, true, false) - of bmMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMask, true, false) - of bmOverwrite: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverwrite, true, false) - of bmSubtractMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSubtractMask, true, false) - of bmIntersectMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmIntersectMask, true, false) - of bmExcludeMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExcludeMask, true, false) + of bmNormal: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmNormal, false) + of bmDarken: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDarken, false) + of bmMultiply: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMultiply, false) + of bmLinearBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearBurn, false) + of bmColorBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorBurn, false) + of bmLighten: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLighten, false) + of bmScreen: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmScreen, false) + of bmLinearDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearDodge, false) + of bmColorDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorDodge, false) + of bmOverlay: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverlay, false) + of bmSoftLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSoftLight, false) + of bmHardLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHardLight, false) + of bmDifference: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDifference, false) + of bmExclusion: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExclusion, false) + of bmHue: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHue, false) + of bmSaturation: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSaturation, false) + of bmColor: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColor, false) + of bmLuminosity: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLuminosity, false) + of bmMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMask, false) + of bmOverwrite: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverwrite, false) + of bmSubtractMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSubtractMask, false) + of bmIntersectMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmIntersectMask, false) + of bmExcludeMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExcludeMask, false) else: case blendMode - of bmNormal: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmNormal, true, true) - of bmDarken: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDarken, true, true) - of bmMultiply: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMultiply, true, true) - of bmLinearBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearBurn, true, true) - of bmColorBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorBurn, true, true) - of bmLighten: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLighten, true, true) - of bmScreen: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmScreen, true, true) - of bmLinearDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearDodge, true, true) - of bmColorDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorDodge, true, true) - of bmOverlay: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverlay, true, true) - of bmSoftLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSoftLight, true, true) - of bmHardLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHardLight, true, true) - of bmDifference: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDifference, true, true) - of bmExclusion: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExclusion, true, true) - of bmHue: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHue, true, true) - of bmSaturation: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSaturation, true, true) - of bmColor: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColor, true, true) - of bmLuminosity: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLuminosity, true, true) - of bmMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMask, true, true) - of bmOverwrite: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverwrite, true, true) - of bmSubtractMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSubtractMask, true, true) - of bmIntersectMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmIntersectMask, true, true) - of bmExcludeMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExcludeMask, true, true) + of bmNormal: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmNormal, true) + of bmDarken: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDarken, true) + of bmMultiply: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMultiply, true) + of bmLinearBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearBurn, true) + of bmColorBurn: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorBurn, true) + of bmLighten: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLighten, true) + of bmScreen: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmScreen, true) + of bmLinearDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLinearDodge, true) + of bmColorDodge: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColorDodge, true) + of bmOverlay: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverlay, true) + of bmSoftLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSoftLight, true) + of bmHardLight: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHardLight, true) + of bmDifference: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmDifference, true) + of bmExclusion: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExclusion, true) + of bmHue: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmHue, true) + of bmSaturation: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSaturation, true) + of bmColor: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmColor, true) + of bmLuminosity: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmLuminosity, true) + of bmMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmMask, true) + of bmOverwrite: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmOverwrite, true) + of bmSubtractMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmSubtractMask, true) + of bmIntersectMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmIntersectMask, true) + of bmExcludeMask: drawUberStatic(a, b, c, start, stepX, stepY, lines, bmExcludeMask, true) proc draw*( a, b: Image, pos = vec2(0, 0), blendMode = bmNormal diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim new file mode 100644 index 0000000..4d6c390 --- /dev/null +++ b/tests/benchmark_images.nim @@ -0,0 +1,79 @@ +import chroma, pixie, fidget/opengl/perf + +const iterations = 100 + +proc fillOriginal(a: Image, rgba: ColorRGBA) = + for y in 0 ..< a.height: + for x in 0 ..< a.width: + a.setRgbaUnsafe(x, y, rgba) + +proc invertOriginal(a: Image) = + for y in 0 ..< a.height: + for x in 0 ..< a.width: + var rgba = a.getRgbaUnsafe(x, y) + rgba.r = 255 - rgba.r + rgba.g = 255 - rgba.g + rgba.b = 255 - rgba.b + rgba.a = 255 - rgba.a + a.setRgbaUnsafe(x, y, rgba) + +proc applyOpacityOriginal(a: Image, opacity: float32): Image = + result = newImage(a.width, a.height) + let op = (255 * opacity).uint32 + for y in 0 ..< a.height: + for x in 0 ..< a.width: + var rgba = a.getRgbaUnsafe(x, y) + rgba.a = ((rgba.a.uint32 * op) div 255).clamp(0, 255).uint8 + result.setRgbaUnsafe(x, y, rgba) + +proc sharpOpacityOriginal(a: Image): Image = + result = newImage(a.width, a.height) + for y in 0 ..< a.height: + for x in 0 ..< a.width: + var rgba = a.getRgbaUnsafe(x, y) + if rgba.a == 0: + result.setRgbaUnsafe(x, y, rgba(0, 0, 0, 0)) + else: + result.setRgbaUnsafe(x, y, rgba(255, 255, 255, 255)) + +timeIt "fillOriginal": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a.fillOriginal(rgba(255, 255, 255, 255)) + doAssert a[0, 0] == rgba(255, 255, 255, 255) + +timeIt "fill": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a.fill(rgba(255, 255, 255, 255)) + doAssert a[0, 0] == rgba(255, 255, 255, 255) + +timeIt "invertOriginal": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a.invertOriginal() + +timeIt "invert": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a.invert() + +timeIt "applyOpacityOriginal": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a = a.applyOpacityOriginal(0.5) + +timeIt "applyOpacity": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a.applyOpacity(0.5) + +timeIt "sharpOpacityOriginal": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a = a.sharpOpacityOriginal() + +timeIt "sharpOpacity": + var a = newImage(2560, 1440) + for i in 0 ..< iterations: + a.sharpOpacity() diff --git a/tests/benchmark_draw.nim b/tests/benchmark_images_draw.nim similarity index 100% rename from tests/benchmark_draw.nim rename to tests/benchmark_images_draw.nim diff --git a/tests/benchmark_shadows.nim b/tests/benchmark_images_shadows.nim similarity index 70% rename from tests/benchmark_shadows.nim rename to tests/benchmark_images_shadows.nim index adb684b..ee42cc1 100644 --- a/tests/benchmark_shadows.nim +++ b/tests/benchmark_images_shadows.nim @@ -4,13 +4,15 @@ timeIt "spread": var tmp = 0 var spread: Image for i in 0 ..< 100: - var a = newImageFill(100, 100, rgba(0, 0, 0, 0)) - var b = newImageFill(50, 50, rgba(0, 0, 0, 255)) + var a = newImage(100, 100) + var b = newImage(50, 50) + b.fill(rgba(0, 0, 0, 255)) a.draw(b, vec2(25, 25)) spread = a.spread(spread = 10) - b = newImageFill(50, 50, rgba(255, 255, 255, 255)) + b = newImage(50, 50) + b.fill(rgba(255, 255, 255, 255)) spread.draw(b, vec2(25, 25)) tmp += spread.width * spread.height @@ -21,13 +23,15 @@ timeIt "blur": var tmp = 0 var blur: Image for i in 0 ..< 100: - var a = newImageFill(100, 100, rgba(0, 0, 0, 0)) - var b = newImageFill(50, 50, rgba(255, 255, 255, 255)) + var a = newImage(100, 100) + var b = newImage(50, 50) + b.fill(rgba(255, 255, 255, 255)) a.draw(b, vec2(25, 25)) blur = a.blur(radius = 10) - b = newImageFill(50, 50, rgba(255, 255, 255, 255)) + b = newImage(50, 50) + b.fill(rgba(255, 255, 255, 255)) blur.draw(b, vec2(25, 25)) tmp += blur.width * blur.height @@ -38,14 +42,16 @@ timeIt "shadow": var tmp = 0 var shadow: Image for i in 0 ..< 100: - var a = newImageFill(100, 100, rgba(0, 0, 0, 0)) - var b = newImageFill(50, 50, rgba(0, 0, 0, 255)) + var a = newImage(100, 100) + var b = newImage(50, 50) + b.fill(rgba(0, 0, 0, 255)) a.draw(b, vec2(25, 25)) shadow = a.shadow( offset = vec2(0, 0), spread = 10, blur = 10, color = rgba(0, 0, 0, 255)) - b = newImageFill(50, 50, rgba(255, 255, 255, 255)) + b = newImage(50, 50) + b.fill(rgba(255, 255, 255, 255)) shadow.draw(b, vec2(25, 25)) tmp += shadow.width * shadow.height @@ -57,8 +63,9 @@ timeIt "shadow": # var tmp = 0 # var shadow: Image # for i in 0 ..< 1: -# var a = newImageFill(10, 200, rgba(0, 0, 0, 0)) -# var b = newImageFill(50, 50, rgba(0, 0, 0, 255)) +# var a = newImage(10, 200) +# var b = newImage(50, 50) +# b.fill(rgba(0, 0, 0, 255)) # a.draw(b, vec2(-25, -25)) # for spread in 0 .. 0: @@ -73,7 +80,8 @@ timeIt "shadow": # for y in 25 ..< (25 + spread + blur).int: # echo y - 25, ":", shadow[5, y].a -# b = newImageFill(50, 50, rgba(255, 255, 255, 255)) +# b = newImage(50, 50) +# b.fill(rgba(255, 255, 255, 255)) # shadow.draw(b, vec2(-25, -25)) # tmp += shadow.width * shadow.height diff --git a/tests/benchmark_inplace.nim b/tests/benchmark_inplace.nim deleted file mode 100644 index 6c9a641..0000000 --- a/tests/benchmark_inplace.nim +++ /dev/null @@ -1,209 +0,0 @@ -import pixie, chroma, vmath, fidget/opengl/perf, pixie/fileformats/bmp - -proc inPlaceDraw*(destImage: Image, srcImage: Image, mat: Mat3, blendMode = bmNormal) = - ## Draws one image onto another using matrix with color blending. - for y in 0 ..< destImage.width: - for x in 0 ..< destImage.height: - let srcPos = mat * vec2(x.float32, y.float32) - let destRgba = destImage.getRgbaUnsafe(x, y) - var rgba = destRgba - var srcRgba = rgba(0, 0, 0, 0) - if srcImage.inside(srcPos.x.floor.int, srcPos.y.floor.int): - srcRgba = srcImage.getRgbaSmooth(srcPos.x - 0.5, srcPos.y - 0.5) - if blendMode.hasEffect(srcRgba): - rgba = blendMode.mix(destRgba, srcRgba) - destImage.setRgbaUnsafe(x, y, rgba) - -proc inPlaceDraw*(destImage: Image, srcImage: Image, pos = vec2(0, 0), blendMode = bmNormal) = - destImage.inPlaceDraw(srcImage, translate(-pos), blendMode) - -proc drawStepperInPlace*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode) = - ## Draws one image onto another using matrix with color blending. - - type Segment = object - ## A math segment from point "at" to point "to" - at*: Vec2 - to*: Vec2 - - proc segment(at, to: Vec2): Segment = - result.at = at - result.to = to - - proc intersects(a, b: Segment, at: var Vec2): bool = - ## Checks if the a segment intersects b segment. - ## If it returns true, at will have point of intersection - var s1x, s1y, s2x, s2y: float32 - s1x = a.to.x - a.at.x - s1y = a.to.y - a.at.y - s2x = b.to.x - b.at.x - s2y = b.to.y - b.at.y - - var s, t: float32 - s = (-s1y * (a.at.x - b.at.x) + s1x * (a.at.y - b.at.y)) / - (-s2x * s1y + s1x * s2y) - t = (s2x * (a.at.y - b.at.y) - s2y * (a.at.x - b.at.x)) / - (-s2x * s1y + s1x * s2y) - - if s >= 0 and s < 1 and t >= 0 and t < 1: - at.x = a.at.x + (t * s1x) - at.y = a.at.y + (t * s1y) - return true - return false - - var - matInv = mat.inverse() - # compute movement vectors - h = 0.5.float32 - start = matInv * vec2(0 + h, 0 + h) - stepX = matInv * vec2(1 + h, 0 + h) - start - stepY = matInv * vec2(0 + h, 1 + h) - start - minFilterBy2 = max(stepX.length, stepY.length) - b = b - - let corners = [ - mat * vec2(0, 0), - mat * vec2(b.width.float32, 0), - mat * vec2(b.width.float32, b.height.float32), - mat * vec2(0, b.height.float32) - ] - - let lines = [ - segment(corners[0], corners[1]), - segment(corners[1], corners[2]), - segment(corners[2], corners[3]), - segment(corners[3], corners[0]) - ] - - while minFilterBy2 > 2.0: - b = b.minifyBy2() - start /= 2 - stepX /= 2 - stepY /= 2 - minFilterBy2 /= 2 - - template forBlend( - mixer: proc(a, b: ColorRGBA): ColorRGBA, - getRgba: proc(a: Image, x, y: float32): ColorRGBA {.inline.}, - ) = - for y in 0 ..< a.height: - var - xMin = 0 - xMax = 0 - hasIntersection = false - for yOffset in [0.float32, 1]: - var scanLine = segment( - vec2(-100000, y.float32 + yOffset), - vec2(10000, y.float32 + yOffset) - ) - for l in lines: - var at: Vec2 - if intersects(l, scanLine, at): - if hasIntersection: - xMin = min(xMin, at.x.floor.int) - xMax = max(xMax, at.x.ceil.int) - else: - hasIntersection = true - xMin = at.x.floor.int - xMax = at.x.ceil.int - - xMin = xMin.clamp(0, a.width) - xMax = xMax.clamp(0, a.width) - - # for x in 0 ..< xMin: - # result.setRgbaUnsafe(x, y, a.getRgbaUnsafe(x, y)) - # if xMin > 0: - # copyMem(a.getAddr(0, y), a.getAddr(0, y), 4*xMin) - - for x in xMin ..< xMax: - let srcV = start + stepX * float32(x) + stepY * float32(y) - var rgba = a.getRgbaUnsafe(x, y) - if b.inside((srcV.x - h).int, (srcV.y - h).int): - let rgba2 = b.getRgba(srcV.x - h, srcV.y - h) - rgba = mixer(rgba, rgba2) - a.setRgbaUnsafe(x, y, rgba) - - #for x in xMax ..< a.width: - # result.setRgbaUnsafe(x, y, a.getRgbaUnsafe(x, y)) - # if a.width - xMax > 0: - # copyMem(result.getAddr(xMax, y), a.getAddr(xMax, y), 4*(a.width - xMax)) - - proc getRgba(a: Image, x, y: float32): ColorRGBA {.inline.} = - a.getRgbaUnsafe(x.int, y.int) - - if stepX.length == 1.0 and stepY.length == 1.0: - case blendMode - of bmNormal: forBlend(blendNormal, getRgba) - of bmDarken: forBlend(blendDarken, getRgba) - of bmMultiply: forBlend(blendMultiply, getRgba) - of bmLinearBurn: forBlend(blendLinearBurn, getRgba) - of bmColorBurn: forBlend(blendColorBurn, getRgba) - of bmLighten: forBlend(blendLighten, getRgba) - of bmScreen: forBlend(blendScreen, getRgba) - of bmLinearDodge: forBlend(blendLinearDodge, getRgba) - of bmColorDodge: forBlend(blendColorDodge, getRgba) - of bmOverlay: forBlend(blendOverlay, getRgba) - of bmSoftLight: forBlend(blendSoftLight, getRgba) - of bmHardLight: forBlend(blendHardLight, getRgba) - of bmDifference: forBlend(blendDifference, getRgba) - of bmExclusion: forBlend(blendExclusion, getRgba) - of bmHue: forBlend(blendHue, getRgba) - of bmSaturation: forBlend(blendSaturation, getRgba) - of bmColor: forBlend(blendColor, getRgba) - of bmLuminosity: forBlend(blendLuminosity, getRgba) - of bmMask: forBlend(blendMask, getRgba) - of bmOverwrite: forBlend(blendOverwrite, getRgba) - of bmSubtractMask: forBlend(blendSubtractMask, getRgba) - of bmIntersectMask: forBlend(blendIntersectMask, getRgba) - of bmExcludeMask: forBlend(blendExcludeMask, getRgba) - else: - case blendMode - of bmNormal: forBlend(blendNormal, getRgbaSmooth) - of bmDarken: forBlend(blendDarken, getRgbaSmooth) - of bmMultiply: forBlend(blendMultiply, getRgbaSmooth) - of bmLinearBurn: forBlend(blendLinearBurn, getRgbaSmooth) - of bmColorBurn: forBlend(blendColorBurn, getRgbaSmooth) - of bmLighten: forBlend(blendLighten, getRgbaSmooth) - of bmScreen: forBlend(blendScreen, getRgbaSmooth) - of bmLinearDodge: forBlend(blendLinearDodge, getRgbaSmooth) - of bmColorDodge: forBlend(blendColorDodge, getRgbaSmooth) - of bmOverlay: forBlend(blendOverlay, getRgbaSmooth) - of bmSoftLight: forBlend(blendSoftLight, getRgbaSmooth) - of bmHardLight: forBlend(blendHardLight, getRgbaSmooth) - of bmDifference: forBlend(blendDifference, getRgbaSmooth) - of bmExclusion: forBlend(blendExclusion, getRgbaSmooth) - of bmHue: forBlend(blendHue, getRgbaSmooth) - of bmSaturation: forBlend(blendSaturation, getRgbaSmooth) - of bmColor: forBlend(blendColor, getRgbaSmooth) - of bmLuminosity: forBlend(blendLuminosity, getRgbaSmooth) - of bmMask: forBlend(blendMask, getRgbaSmooth) - of bmOverwrite: forBlend(blendOverwrite, getRgbaSmooth) - of bmSubtractMask: forBlend(blendSubtractMask, getRgbaSmooth) - of bmIntersectMask: forBlend(blendIntersectMask, getRgbaSmooth) - of bmExcludeMask: forBlend(blendExcludeMask, getRgbaSmooth) - -timeIt "inPlaceDraw": - var tmp = 0 - for i in 0 ..< 1000: - var a = newImageFill(1000, 1000, rgba(0, 255, 0, 255)) - var b = newImageFill(100, 100, rgba(0, 255, 0, 255)) - a.inPlaceDraw(b, pos=vec2(25, 25)) - tmp += a.width * a.height - echo tmp - -timeIt "drawStepper": - var tmp = 0 - for i in 0 ..< 1000: - var a = newImageFill(1000, 1000, rgba(255, 0, 0, 255)) - var b = newImageFill(100, 100, rgba(0, 255, 0, 255)) - var c = a.drawStepper(b, translate(vec2(25, 25)), bmNormal) - tmp += c.width * c.height - echo tmp - -timeIt "drawStepperInPlace": - var tmp = 0 - for i in 0 ..< 1000: - var a = newImageFill(1000, 1000, rgba(0, 255, 0, 255)) - var b = newImageFill(100, 100, rgba(0, 255, 0, 255)) - drawStepperInPlace(a, b, translate(vec2(25, 25)), bmNormal) - tmp += a.width * a.height - echo tmp diff --git a/tests/images/blur1.png b/tests/images/blur1.png index 469632a..c129e3b 100644 Binary files a/tests/images/blur1.png and b/tests/images/blur1.png differ diff --git a/tests/images/shadow1.png b/tests/images/shadow1.png index 62ac574..3ae66d5 100644 Binary files a/tests/images/shadow1.png and b/tests/images/shadow1.png differ diff --git a/tests/images/spread1.png b/tests/images/spread1.png index 24b105a..3522111 100644 Binary files a/tests/images/spread1.png and b/tests/images/spread1.png differ