From e1a4d7edc403d9816601538ac4f34b4abc21c898 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Thu, 3 Dec 2020 14:21:58 -0600 Subject: [PATCH] more + benchmark --- src/pixie/images.nim | 8 +- tests/benchmark_images.nim | 40 ++++ ...ark_draw.nim => benchmark_images_draw.nim} | 0 ...adows.nim => benchmark_images_shadows.nim} | 32 ++- tests/benchmark_inplace.nim | 209 ------------------ tests/images/blur1.png | Bin 2211 -> 2212 bytes tests/images/shadow1.png | Bin 2118 -> 2115 bytes tests/images/spread1.png | Bin 382 -> 382 bytes 8 files changed, 63 insertions(+), 226 deletions(-) create mode 100644 tests/benchmark_images.nim rename tests/{benchmark_draw.nim => benchmark_images_draw.nim} (100%) rename tests/{benchmark_shadows.nim => benchmark_images_shadows.nim} (70%) delete mode 100644 tests/benchmark_inplace.nim diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 5a10aa6..61886e8 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -70,18 +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 i in 0 ..< image.data.len: - image.data[i] = rgba + for c in image.data.mitems: + c = rgba proc invert*(image: Image) = ## Inverts all of the colors and alpha. - for i in 0 ..< image.data.len: - var rgba = image.data[i] + 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 - image.data[i] = rgba proc subImage*(image: Image, x, y, w, h: int): Image = ## Gets a sub image of the main image. diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim new file mode 100644 index 0000000..6db1b6a --- /dev/null +++ b/tests/benchmark_images.nim @@ -0,0 +1,40 @@ +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) + +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() 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 469632a7373d76d8492d26b94361977ddcd1e5ed..c129e3bc9ac49bf5a81ba2de03871426fe14fe06 100644 GIT binary patch delta 2070 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 diff --git a/tests/images/shadow1.png b/tests/images/shadow1.png index 62ac5740bfdbf79ab34ddddd7c8adc028a797ffb..3ae66d559a90ad82737bbad7495c5ad2145a0298 100644 GIT binary patch delta 1099 zcmV-R1ho6c5W^6VBrFO^L_t(|0qxx9PQ)+}h0&pRcx8BIdPgT>kV(8t6X2|TrMu%K zj?4X${h6^)Q3GgUdkci`t>79E{n@=6&(F^rr>CdubNAWV86cE@RW%^_c6Od-!Aq0w(kOUQf{|SV~-^}vKJ|8FZ89yH@^YOB*&avzIy?wmQK)%Fg zGjn&u+sDUzgv`gteBRH;$$Z|=$I5)T>>}c=I>)Z-r@#B$?;~atef0xo0v9|GoKT$e zH90VRea^Uma>|D0Zb7n8*;nF(?W=Ku2ZR%fa}0*Q?r}dDvY1G4H3`yx)9t#aorRr1 z001N_7z~EK?q%bU$t_~;7A6Z;z=8&Zf#8Ipg2DQ+``j-FnM@?`ngl6O*ICdBMDPrV z3JOrJgQ35B$)qM{3z@qG>Td@O7V`M*I3N9Gxvro7?v;T&WHXWQYDyr26*AYL0K#Cn zZanvp2~D21nYml2X(4-mGrR$uZwDaW4#&;|qM!S6|33mE_kGW$s57~)`x{&0s+FY^RDY_5;iFa zYy%dg&dhNe@y^!~aT76A?W>5mh=|LGI4_*%T-VPycMB91PrzSEU!~(jN4>jlbDQuO%8^|FaQ>}Di%1~kIF!>HoIou_AiTsQNAHnEdoMd)V%)scSc~a ziNG*9TZkvaqH=NS+?`?DM*>hC=QY*7WgrV41Cbd|ve@5K0-;;A|pK03d83X-^5~LRYco{#4GrCk)xhNLF%Ns%1cy$-pLH zlbMt)BAp8>7n$egT5~UD&9*(+yN8nHA)BO$lAs|ZSpgEDs7Lm3Xwm{Zi1=|bTOIB^R_pI77VR@xVd!ar9 zgi0s|9NWa@BD7c0W#Hre$zW_b(YP+uXMv1WPM~ceS+#k5?A$6Dklau1wXE8yvb|K_ z1wuKAT64MB+V#1f`)FTeZ{N~SfP7XKw@Ut1{RNOBt}mzqX-pkRW9mQ}(+hO13ghCh R8sY!|002ovPDHLkV1i9E4hjGO delta 1094 zcmV-M1iAae5XKOYBrFX{L_t(|0qxvpPsA_~gyE%k_{;E{=^Z^0i%jA>ngHkIk>=J( z9G7{M`SSyezA8?BivVzXTh9z|4ore7Ni);;lO86MwGjr@#B$?;~atedhyaf(L>VigRFr z;p=n81(Z`ZGY*b|N9x%&4XfdJvydDrzd37ZrIwgC%L zXXdz#c<1YgxQQ65_Ekh&M8su8oEOe>uIp!l?(ayoLnj|N*0JfOY7P-o{SNl_0 z1~SPiqs&WPs*ivW5T$a^oJeJhNLvhBAZxFJ?S<_nt2WzvR&ANEywaq-P@e%pB@_dW zZQ^om5!$QhGVt;KWH7dzXj~WSvp~iwC(yQ#tlB(2c5amnNbV>1T2}2;*Bl zt+`xm?fP8LeY7vKw{PhuKt3yrTP6Rh{sKtX7u10?rVgYrbs&xD1$bQwAO4{y00000 M07*qoM6N<$f`6F&D*ylh diff --git a/tests/images/spread1.png b/tests/images/spread1.png index 24b105a0394e18f8d48fd8936e13e04aa630cf69..352211170be05b76e5e7a03f69f33bf23c9e2c03 100644 GIT binary patch literal 382 zcmeAS@N?(olHy`uVBq!ia0vp^DImZLY{r?pl4qi=-}7!d z_Fx`{N+0VJn{<+AEt@~TvhK$m`M8SL%JHwVdz1xK+$KmkCb4u1(w<@IGvTXapMihI z|G;FVdQ&MBb@05<)D@Bjb+ literal 382 zcmeAS@N?(olHy`uVBq!ia0vp^DIm&l#zQ^rft-oi6p8GmZpkL2iZ(3t+mb|(7?!4Oe4@tQE4R*vF Z#yQNpK1c|5Z2*QVgQu&X%Q~loCICIdg%JP%