From de13c129e43aa3f905cae8a1da9b6e79ec8dd959 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Sun, 12 Dec 2021 21:48:02 -0600 Subject: [PATCH 01/11] isOpaque --- experiments/benchmark_cairo_draw.nim | 10 ++++++--- src/pixie/images.nim | 32 ++++++++++++++++++++++++---- tests/benchmark_images.nim | 4 ++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/experiments/benchmark_cairo_draw.nim b/experiments/benchmark_cairo_draw.nim index 0538d0c..3444978 100644 --- a/experiments/benchmark_cairo_draw.nim +++ b/experiments/benchmark_cairo_draw.nim @@ -8,6 +8,12 @@ block: ctx = tmp.create() timeIt "cairo draw basic": + # 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) @@ -22,10 +28,8 @@ block: source = readImage("tests/fileformats/svg/masters/Ghostscript_Tiger.png") tmp = newImage(1568, 940) - timeIt "isOneColor": - doAssert not backdrop.isOneColor() - timeIt "pixie draw basic": + # tmp.fill(rgbx(127, 127, 127, 255)) tmp.draw(backdrop) tmp.draw(source) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index a863331..3062b7c 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -148,7 +148,7 @@ proc isOneColor*(image: Image): bool {.raises: [].} = values1 = mm_loadu_si128(image.data[i + 4].addr) mask0 = mm_movemask_epi8(mm_cmpeq_epi8(values0, colorVec)) mask1 = mm_movemask_epi8(mm_cmpeq_epi8(values1, colorVec)) - if mask0 != uint16.high.int or mask1 != uint16.high.int: + if mask0 != 0xffff or mask1 != 0xffff: return false i += 8 @@ -162,7 +162,7 @@ proc isTransparent*(image: Image): bool {.raises: [].} = var i: int when defined(amd64) and not defined(pixieNoSimd): - let zeroVec = mm_setzero_si128() + let vecZero = mm_setzero_si128() for _ in 0 ..< image.data.len div 16: let values0 = mm_loadu_si128(image.data[i + 0].addr) @@ -172,8 +172,7 @@ proc isTransparent*(image: Image): bool {.raises: [].} = values01 = mm_or_si128(values0, values1) values23 = mm_or_si128(values2, values3) values = mm_or_si128(values01, values23) - mask = mm_movemask_epi8(mm_cmpeq_epi8(values, zeroVec)) - if mask != uint16.high.int: + if mm_movemask_epi8(mm_cmpeq_epi8(values, vecZero)) != 0xffff: return false i += 16 @@ -181,6 +180,31 @@ proc isTransparent*(image: Image): bool {.raises: [].} = if image.data[j].a != 0: return false +proc isOpaque*(image: Image): bool {.raises: [].} = + result = true + + var i: int + when defined(amd64) and not defined(pixieNoSimd): + let + vec255 = mm_set1_epi32(cast[int32](uint32.high)) + colorMask = mm_set1_epi32(cast[int32]([255.uint8, 255, 255, 0])) + for _ in 0 ..< image.data.len div 16: + let + values0 = mm_loadu_si128(image.data[i + 0].addr) + values1 = mm_loadu_si128(image.data[i + 4].addr) + values2 = mm_loadu_si128(image.data[i + 8].addr) + values3 = mm_loadu_si128(image.data[i + 12].addr) + values01 = mm_and_si128(values0, values1) + values23 = mm_and_si128(values2, values3) + values = mm_or_si128(mm_and_si128(values01, values23), colorMask) + if mm_movemask_epi8(mm_cmpeq_epi8(values, vec255)) != 0xffff: + return false + i += 16 + + for j in i ..< image.data.len: + if image.data[j].a != 255: + return false + proc flipHorizontal*(image: Image) {.raises: [].} = ## Flips the image around the Y axis. let w = image.width div 2 diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim index 728397b..7be2761 100644 --- a/tests/benchmark_images.nim +++ b/tests/benchmark_images.nim @@ -25,6 +25,10 @@ image.fill(rgba(0, 0, 0, 0)) timeIt "isTransparent": doAssert image.isTransparent() +image.fill(rgba(255, 255, 255, 255)) +timeIt "isOpaque": + doAssert image.isOpaque() + reset() timeIt "subImage": From 64e6db2cef8613d54c4146c1da0618db09ed8d41 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Sun, 12 Dec 2021 23:01:35 -0600 Subject: [PATCH 02/11] nonsimd path --- src/pixie/blends.nim | 2 +- src/pixie/images.nim | 87 ++++++++++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 371b50f..0eb83c8 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -165,7 +165,7 @@ proc SetSat(C: Color, s: float32): Color {.inline.} = if satC > 0: result = (C - min([C.r, C.g, C.b])) * s / satC -proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX = +proc blendNormal*(backdrop, source: ColorRGBX): ColorRGBX = if backdrop.a == 0 or source.a == 255: return source if source.a == 0: diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 3062b7c..2a8ea84 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -913,29 +913,70 @@ proc drawUber( clamp(srcPos.y, 0, b.height.float32) ) - for x in x ..< xMax: - let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) - - when type(a) is Image: - let backdrop = a.unsafe[x, y] - when type(b) is Image: - let - sample = b.unsafe[samplePos.x, samplePos.y] - blended = blender(backdrop, sample) - else: # b is a Mask - let - sample = b.unsafe[samplePos.x, samplePos.y] - blended = blender(backdrop, rgbx(0, 0, 0, sample)) - a.unsafe[x, y] = blended - else: # a is a Mask - let backdrop = a.unsafe[x, y] - when type(b) is Image: - let sample = b.unsafe[samplePos.x, samplePos.y].a - else: # b is a Mask - let sample = b.unsafe[samplePos.x, samplePos.y] - a.unsafe[x, y] = masker(backdrop, sample) - - srcPos += dx + if blendMode == bmOverwrite: + for x in x ..< xMax: + let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) + when type(a) is Image: + when type(b) is Image: + let source = b.unsafe[samplePos.x, samplePos.y] + else: # b is a Mask + let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y]) + a.unsafe[x, y] = source + else: # a is a Mask + when type(b) is Image: + let source = b.unsafe[samplePos.x, samplePos.y].a + else: # b is a Mask + let source = b.unsafe[samplePos.x, samplePos.y] + a.unsafe[x, y] = source + srcPos += dx + elif blendMode == bmNormal: + for x in x ..< xMax: + let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) + when type(a) is Image: + when type(b) is Image: + let source = b.unsafe[samplePos.x, samplePos.y] + else: # b is a Mask + let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y]) + if source.a > 0: + if source.a == 255: + a.unsafe[x, y] = source + else: + let backdrop = a.unsafe[x, y] + a.unsafe[x, y] = blendNormal(backdrop, source) + else: # a is a Mask + when type(b) is Image: + let source = b.unsafe[samplePos.x, samplePos.y].a + else: # b is a Mask + let source = b.unsafe[samplePos.x, samplePos.y] + if source > 0: + if source == 255: + a.unsafe[x, y] = source + else: + let backdrop = a.unsafe[x, y] + a.unsafe[x, y] = blendAlpha(backdrop, source) + srcPos += dx + else: + for x in x ..< xMax: + let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) + when type(a) is Image: + let backdrop = a.unsafe[x, y] + when type(b) is Image: + let + sample = b.unsafe[samplePos.x, samplePos.y] + blended = blender(backdrop, sample) + else: # b is a Mask + let + sample = b.unsafe[samplePos.x, samplePos.y] + blended = blender(backdrop, rgbx(0, 0, 0, sample)) + a.unsafe[x, y] = blended + else: # a is a Mask + let backdrop = a.unsafe[x, y] + when type(b) is Image: + let sample = b.unsafe[samplePos.x, samplePos.y].a + else: # b is a Mask + let sample = b.unsafe[samplePos.x, samplePos.y] + a.unsafe[x, y] = masker(backdrop, sample) + srcPos += dx if blendMode == bmMask: if a.width - xMax > 0: From ac6e6f621442c373d08491b8cef338adf7fcb484 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 00:15:44 -0600 Subject: [PATCH 03/11] mat -> transform --- src/pixie/images.nim | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 2a8ea84..c73eb85 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -660,7 +660,7 @@ proc getRgbaSmooth*( topMix proc drawCorrect( - a, b: Image | Mask, mat = mat3(), tiled = false, blendMode = bmNormal + a, b: Image | Mask, transform = mat3(), tiled = false, blendMode = bmNormal ) {.raises: [PixieError].} = ## Draws one image onto another using matrix with color blending. @@ -670,11 +670,11 @@ proc drawCorrect( let masker = blendMode.masker() var - matInv = mat.inverse() + inverseTransform = transform.inverse() # Compute movement vectors - p = matInv * vec2(0 + h, 0 + h) - dx = matInv * vec2(1 + h, 0 + h) - p - dy = matInv * vec2(0 + h, 1 + h) - p + 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 @@ -684,7 +684,7 @@ proc drawCorrect( dx /= 2 dy /= 2 filterBy2 /= 2 - matInv = scale(vec2(1/2, 1/2)) * matInv + inverseTransform = scale(vec2(1/2, 1/2)) * inverseTransform while filterBy2 <= 0.5: b = b.magnifyBy2() @@ -692,12 +692,12 @@ proc drawCorrect( dx *= 2 dy *= 2 filterBy2 *= 2 - matInv = scale(vec2(2, 2)) * matInv + inverseTransform = scale(vec2(2, 2)) * inverseTransform for y in 0 ..< a.height: for x in 0 ..< a.width: let - samplePos = matInv * vec2(x.float32 + h, y.float32 + h) + samplePos = inverseTransform * vec2(x.float32 + h, y.float32 + h) xFloat = samplePos.x - h yFloat = samplePos.y - h @@ -721,14 +721,14 @@ proc drawCorrect( a.setValueUnsafe(x, y, masker(backdrop, sample)) proc drawUber( - a, b: Image | Mask, mat = mat3(), blendMode = bmNormal + a, b: Image | Mask, transform = mat3(), blendMode: BlendMode ) {.raises: [PixieError].} = 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) + 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]), @@ -738,11 +738,11 @@ proc drawUber( ] var - matInv = mat.inverse() + inverseTransform = transform.inverse() # Compute movement vectors - p = matInv * vec2(0 + h, 0 + h) - dx = matInv * vec2(1 + h, 0 + h) - p - dy = matInv * vec2(0 + h, 1 + h) - p + 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 @@ -763,15 +763,10 @@ proc drawUber( let smooth = not( dx.length == 1.0 and dy.length == 1.0 and - mat[2, 0].fractional == 0.0 and - mat[2, 1].fractional == 0.0 + transform[2, 0].fractional == 0.0 and + transform[2, 1].fractional == 0.0 ) - when type(a) is Image: - let blender = blendMode.blender() - else: # a is a Mask - let masker = blendMode.masker() - # Determine where we should start and stop drawing in the y dimension var yMin, yMax: int if blendMode == bmMask: @@ -787,6 +782,11 @@ proc drawUber( yMin = yMin.clamp(0, a.height) yMax = yMax.clamp(0, a.height) + when type(a) is Image: + let blender = blendMode.blender() + else: # a is a Mask + let masker = blendMode.masker() + for y in yMin ..< yMax: # Determine where we should start and stop drawing in the x dimension var From be598a108b38d94cf62fc4595bf1a8e7859bb343 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 00:29:51 -0600 Subject: [PATCH 04/11] bmMask nonsimd draw faster --- experiments/benchmark_cairo_draw.nim | 32 ++++++++++++++++++++++++++++ src/pixie/blends.nim | 2 +- src/pixie/images.nim | 24 +++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/experiments/benchmark_cairo_draw.nim b/experiments/benchmark_cairo_draw.nim index 3444978..d6db25b 100644 --- a/experiments/benchmark_cairo_draw.nim +++ b/experiments/benchmark_cairo_draw.nim @@ -35,6 +35,38 @@ block: # tmp.writeFile("tmp2.png") +block: + let + backdrop = imageSurfaceCreateFromPng("tests/fileformats/svg/masters/dragon2.png") + source = imageSurfaceCreateFromPng("tests/fileformats/svg/masters/Ghostscript_Tiger.png") + tmp = imageSurfaceCreate(FORMAT_ARGB32, 1568, 940) + 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") + +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 mask": + tmp.draw(backdrop) + tmp.draw(source, blendMode = bmMask) + + tmp.writeFile("tmp_masked2.png") + block: let backdrop = imageSurfaceCreateFromPng("tests/fileformats/svg/masters/dragon2.png") diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 0eb83c8..316b289 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -419,7 +419,7 @@ proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX = blended = SetLum(SetSat(backdrop, Sat(source)), Lum(backdrop)) result = alphaFix(backdrop, source, blended).rgba.rgbx() -proc blendMask(backdrop, source: ColorRGBX): ColorRGBX = +proc blendMask*(backdrop, source: ColorRGBX): ColorRGBX = let k = source.a.uint32 result.r = ((backdrop.r * k) div 255).uint8 result.g = ((backdrop.g * k) div 255).uint8 diff --git a/src/pixie/images.nim b/src/pixie/images.nim index c73eb85..20eb4e5 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -955,6 +955,30 @@ proc drawUber( let backdrop = a.unsafe[x, y] a.unsafe[x, y] = blendAlpha(backdrop, source) srcPos += dx + elif blendMode == bmMask: + for x in x ..< xMax: + let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) + when type(a) is Image: + when type(b) is Image: + let source = b.unsafe[samplePos.x, samplePos.y] + else: # b is a Mask + let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y]) + if source.a == 0: + a.unsafe[x, y] = rgbx(0, 0, 0, 0) + elif source.a != 255: + let backdrop = a.unsafe[x, y] + a.unsafe[x, y] = blendMask(backdrop, source) + else: # a is a Mask + when type(b) is Image: + let source = b.unsafe[samplePos.x, samplePos.y].a + else: # b is a Mask + let source = b.unsafe[samplePos.x, samplePos.y] + if source == 0: + a.unsafe[x, y] = 0 + elif source != 255: + let backdrop = a.unsafe[x, y] + a.unsafe[x, y] = blendAlpha(backdrop, source) + srcPos += dx else: for x in x ..< xMax: let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) From 1352ff73b0ecc0df98f4adcf7ebf7548abb32a48 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 00:31:35 -0600 Subject: [PATCH 05/11] case --- experiments/benchmark_cairo_draw.nim | 4 ++-- src/pixie/images.nim | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/experiments/benchmark_cairo_draw.nim b/experiments/benchmark_cairo_draw.nim index d6db25b..f2d9fe9 100644 --- a/experiments/benchmark_cairo_draw.nim +++ b/experiments/benchmark_cairo_draw.nim @@ -53,7 +53,7 @@ block: ctx.mask(source, 0, 0) tmp.flush() - echo tmp.writeToPng("tmp_masked.png") + # echo tmp.writeToPng("tmp_masked.png") block: let @@ -65,7 +65,7 @@ block: tmp.draw(backdrop) tmp.draw(source, blendMode = bmMask) - tmp.writeFile("tmp_masked2.png") + # tmp.writeFile("tmp_masked2.png") block: let diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 20eb4e5..56c7d8e 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -913,7 +913,8 @@ proc drawUber( clamp(srcPos.y, 0, b.height.float32) ) - if blendMode == bmOverwrite: + case blendMode: + of bmOverwrite: for x in x ..< xMax: let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) when type(a) is Image: @@ -929,7 +930,7 @@ proc drawUber( let source = b.unsafe[samplePos.x, samplePos.y] a.unsafe[x, y] = source srcPos += dx - elif blendMode == bmNormal: + of bmNormal: for x in x ..< xMax: let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) when type(a) is Image: @@ -955,7 +956,7 @@ proc drawUber( let backdrop = a.unsafe[x, y] a.unsafe[x, y] = blendAlpha(backdrop, source) srcPos += dx - elif blendMode == bmMask: + of bmMask: for x in x ..< xMax: let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) when type(a) is Image: From ba6ea448e4a421bdcbe5ff27ef4d5e3d14294153 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 00:36:47 -0600 Subject: [PATCH 06/11] f --- src/pixie/blends.nim | 2 +- src/pixie/images.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 316b289..080bb1f 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -477,7 +477,7 @@ proc maskNormal(backdrop, source: uint8): uint8 = ## Blending masks blendAlpha(backdrop, source) -proc maskMask(backdrop, source: uint8): uint8 = +proc maskMask*(backdrop, source: uint8): uint8 = ## Masking masks ((backdrop.uint32 * source) div 255).uint8 diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 56c7d8e..bcd952d 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -978,7 +978,7 @@ proc drawUber( a.unsafe[x, y] = 0 elif source != 255: let backdrop = a.unsafe[x, y] - a.unsafe[x, y] = blendAlpha(backdrop, source) + a.unsafe[x, y] = maskMask(backdrop, source) srcPos += dx else: for x in x ..< xMax: From 38798e503b0f3898e5ce848f53b1729525564f5f Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 00:39:13 -0600 Subject: [PATCH 07/11] f --- src/pixie/blends.nim | 5 ++++- src/pixie/images.nim | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 080bb1f..d2f927e 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -477,10 +477,13 @@ proc maskNormal(backdrop, source: uint8): uint8 = ## Blending masks blendAlpha(backdrop, source) -proc maskMask*(backdrop, source: uint8): uint8 = +proc maskMaskInline*(backdrop, source: uint8): uint8 {.inline.} = ## Masking masks ((backdrop.uint32 * source) div 255).uint8 +proc maskMask(backdrop, source: uint8): uint8 = + maskMaskInline(backdrop, source) + proc maskSubtract(backdrop, source: uint8): uint8 = ((backdrop.uint32 * (255 - source)) div 255).uint8 diff --git a/src/pixie/images.nim b/src/pixie/images.nim index bcd952d..f791c64 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -978,7 +978,7 @@ proc drawUber( a.unsafe[x, y] = 0 elif source != 255: let backdrop = a.unsafe[x, y] - a.unsafe[x, y] = maskMask(backdrop, source) + a.unsafe[x, y] = maskMaskInline(backdrop, source) srcPos += dx else: for x in x ..< xMax: From 12148618707735f20197ce3640dc6355fb897b0d Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 01:23:14 -0600 Subject: [PATCH 08/11] move blendMode dont export blends --- src/pixie.nim | 11 +++++------ src/pixie/blends.nim | 25 ------------------------- src/pixie/common.nim | 25 +++++++++++++++++++++++++ src/pixie/contexts.nim | 4 ++-- src/pixie/paints.nim | 2 +- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/pixie.nim b/src/pixie.nim index 00b9190..a9fac34 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -1,10 +1,9 @@ -import bumpy, chroma, flatty/binny, os, pixie/blends, pixie/common, - pixie/contexts, pixie/fileformats/bmp, pixie/fileformats/gif, - pixie/fileformats/jpg, pixie/fileformats/png, pixie/fileformats/svg, - pixie/fonts, pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils, vmath +import bumpy, chroma, flatty/binny, os, pixie/common, pixie/contexts, + pixie/fileformats/bmp, pixie/fileformats/gif, pixie/fileformats/jpg, + pixie/fileformats/png, pixie/fileformats/svg, pixie/fonts, pixie/images, + pixie/masks, pixie/paints, pixie/paths, strutils, vmath -export blends, bumpy, chroma, common, contexts, fonts, images, masks, paints, - paths, vmath +export bumpy, chroma, common, contexts, fonts, images, masks, paints, paths, vmath type FileFormat* = enum diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index d2f927e..b9f148b 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -9,31 +9,6 @@ when defined(amd64) and not defined(pixieNoSimd): # See https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt type - BlendMode* = enum - bmNormal - bmDarken - bmMultiply - # bmLinearBurn - bmColorBurn - bmLighten - bmScreen - # bmLinearDodge - bmColorDodge - bmOverlay - bmSoftLight - bmHardLight - bmDifference - bmExclusion - bmHue - bmSaturation - bmColor - bmLuminosity - - bmMask ## Special blend mode that is used for masking - bmOverwrite ## Special blend mode that just copies pixels - bmSubtractMask ## Inverse mask - bmExcludeMask - Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX {.gcsafe, raises: [].} ## Function signature returned by blender. Masker* = proc(backdrop, source: uint8): uint8 {.gcsafe, raises: [].} diff --git a/src/pixie/common.nim b/src/pixie/common.nim index 4e11656..a3f7eea 100644 --- a/src/pixie/common.nim +++ b/src/pixie/common.nim @@ -3,6 +3,31 @@ import bumpy, chroma, vmath type PixieError* = object of ValueError ## Raised if an operation fails. + BlendMode* = enum + bmNormal + bmDarken + bmMultiply + # bmLinearBurn + bmColorBurn + bmLighten + bmScreen + # bmLinearDodge + bmColorDodge + bmOverlay + bmSoftLight + bmHardLight + bmDifference + bmExclusion + bmHue + bmSaturation + bmColor + bmLuminosity + + bmMask ## Special blend mode that is used for masking + bmOverwrite ## Special blend mode that just copies pixels + bmSubtractMask ## Inverse mask + bmExcludeMask + proc mix*(a, b: uint8, t: float32): uint8 {.inline, raises: [].} = ## Linearly interpolate between a and b using t. let t = round(t * 255).uint32 diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 133d57c..268558c 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -1,5 +1,5 @@ -import bumpy, chroma, pixie/blends, pixie/common, pixie/fonts, pixie/images, - pixie/masks, pixie/paints, pixie/paths, tables, vmath +import bumpy, chroma, pixie/common, pixie/fonts, pixie/images, pixie/masks, + pixie/paints, pixie/paths, tables, vmath ## This file provides a Nim version of the Canvas 2D API commonly used on the ## web. The goal is to make picking up Pixie easy for developers familiar with diff --git a/src/pixie/paints.nim b/src/pixie/paints.nim index ba81df6..6e2c247 100644 --- a/src/pixie/paints.nim +++ b/src/pixie/paints.nim @@ -1,4 +1,4 @@ -import blends, chroma, common, images, vmath +import chroma, common, images, vmath when defined(amd64) and not defined(pixieNoSimd): import nimsimd/sse2 From 85780637fcf0af24771180ca36a3ff5cd770bf6b Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 02:25:58 -0600 Subject: [PATCH 09/11] mask yMin yMax better --- src/pixie/images.nim | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index f791c64..00bc72a 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -768,17 +768,12 @@ proc drawUber( ) # Determine where we should start and stop drawing in the y dimension - var yMin, yMax: int - if blendMode == bmMask: - yMin = 0 - yMax = a.height - else: + 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) - + 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) @@ -787,6 +782,10 @@ proc drawUber( else: # a is a Mask let masker = blendMode.masker() + if blendMode == bmMask: + 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 @@ -1007,6 +1006,10 @@ proc drawUber( if a.width - xMax > 0: zeroMem(a.data[a.dataIndex(xMax, y)].addr, 4 * (a.width - xMax)) + if blendMode == bmMask: + 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 = bmNormal ) {.inline, raises: [PixieError].} = From 5199b83c32c5dc277cffee5960d14b103a4fca5f Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 02:35:42 -0600 Subject: [PATCH 10/11] drawBasic --- experiments/benchmark_cairo_draw.nim | 56 ++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/experiments/benchmark_cairo_draw.nim b/experiments/benchmark_cairo_draw.nim index f2d9fe9..98b76b3 100644 --- a/experiments/benchmark_cairo_draw.nim +++ b/experiments/benchmark_cairo_draw.nim @@ -1,4 +1,41 @@ -import benchy, cairo, pixie +import benchy, cairo, pixie, pixie/blends + +when defined(amd64) and not defined(pixieNoSimd): + import nimsimd/sse2 + +when defined(release): + {.push checks: off.} + +proc drawBasic(backdrop, source: Image) = + let sourceIsOpaque = source.isOpaque() + + for y in 0 ..< min(backdrop.height, source.height): + if sourceIsOpaque: + 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.} block: let @@ -7,7 +44,7 @@ block: tmp = imageSurfaceCreate(FORMAT_ARGB32, 1568, 940) ctx = tmp.create() - timeIt "cairo draw basic": + timeIt "cairo draw normal": # ctx.setSourceRgba(0.5, 0.5, 0.5, 1) # let operator = ctx.getOperator() # ctx.setOperator(OperatorSource) @@ -28,13 +65,26 @@ block: source = readImage("tests/fileformats/svg/masters/Ghostscript_Tiger.png") tmp = newImage(1568, 940) - timeIt "pixie draw basic": + timeIt "pixie draw normal": # tmp.fill(rgbx(127, 127, 127, 255)) tmp.draw(backdrop) 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") From 805857df3fd97ab5a9426f9f7d632e5858beb2dd Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 13 Dec 2021 03:00:38 -0600 Subject: [PATCH 11/11] bmOverwrite nonsimd benchmark --- experiments/benchmark_cairo_draw.nim | 13 +++++++++++++ src/pixie/images.nim | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/experiments/benchmark_cairo_draw.nim b/experiments/benchmark_cairo_draw.nim index 98b76b3..c2f6cdf 100644 --- a/experiments/benchmark_cairo_draw.nim +++ b/experiments/benchmark_cairo_draw.nim @@ -72,6 +72,19 @@ block: # 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 overwrite": + # tmp.fill(rgbx(127, 127, 127, 255)) + tmp.draw(backdrop, blendMode = bmOverwrite) + tmp.draw(source) + + # tmp.writeFile("tmp2.png") + block: let backdrop = readImage("tests/fileformats/svg/masters/dragon2.png") diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 00bc72a..cb8e1eb 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -921,13 +921,15 @@ proc drawUber( let source = b.unsafe[samplePos.x, samplePos.y] else: # b is a Mask let source = rgbx(0, 0, 0, b.unsafe[samplePos.x, samplePos.y]) - a.unsafe[x, y] = source + if source.a > 0: + a.unsafe[x, y] = source else: # a is a Mask when type(b) is Image: let source = b.unsafe[samplePos.x, samplePos.y].a else: # b is a Mask let source = b.unsafe[samplePos.x, samplePos.y] - a.unsafe[x, y] = source + if source > 0: + a.unsafe[x, y] = source srcPos += dx of bmNormal: for x in x ..< xMax: