Merge pull request #27 from guzba/master

faster fill, invert, draw
This commit is contained in:
treeform 2020-12-08 19:57:14 -08:00 committed by GitHub
commit 21f4560092
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 270 additions and 315 deletions

View file

@ -10,3 +10,4 @@ requires "vmath >= 0.3.3"
requires "chroma >= 0.1.5" requires "chroma >= 0.1.5"
requires "zippy >= 0.3.5" requires "zippy >= 0.3.5"
requires "flatty >= 0.1.2" requires "flatty >= 0.1.2"
requires "nimsimd >= 0.4.6"

View file

@ -1,34 +1,37 @@
## Blending modes. ## Blending modes.
import chroma, math, algorithm import chroma, math
# See https://www.w3.org/TR/compositing-1/ # See https://www.w3.org/TR/compositing-1/
# See https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt # See https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt
type BlendMode* = enum type
bmNormal BlendMode* = enum
bmDarken bmNormal
bmMultiply bmDarken
bmLinearBurn bmMultiply
bmColorBurn bmLinearBurn
bmLighten bmColorBurn
bmScreen bmLighten
bmLinearDodge bmScreen
bmColorDodge bmLinearDodge
bmOverlay bmColorDodge
bmSoftLight bmOverlay
bmHardLight bmSoftLight
bmDifference bmHardLight
bmExclusion bmDifference
bmHue bmExclusion
bmSaturation bmHue
bmColor bmSaturation
bmLuminosity bmColor
bmLuminosity
bmMask ## Special blend mode that is used for masking bmMask ## Special blend mode that is used for masking
bmOverwrite ## Special that does not blend but copies the pixels from target. bmOverwrite ## Special that does not blend but copies the pixels from target.
bmSubtractMask ## Inverse mask bmSubtractMask ## Inverse mask
bmIntersectMask bmIntersectMask
bmExcludeMask bmExcludeMask
Mixer* = proc(a, b: ColorRGBA): ColorRGBA
proc `+`*(a, b: Color): Color {.inline.} = proc `+`*(a, b: Color): Color {.inline.} =
result.r = a.r + b.r result.r = a.r + b.r
@ -123,202 +126,176 @@ proc alphaFix(Cb, Cs, mixed: Color): Color {.inline.} =
result.g /= result.a result.g /= result.a
result.b /= result.a result.b /= result.a
proc blendDarken(Cb, Cs: float32): float32 {.inline.} = proc blendDarkenFloat(Cb, Cs: float32): float32 {.inline.} =
min(Cb, Cs) min(Cb, Cs)
proc blendMultiply(Cb, Cs: float32): float32 {.inline.} = proc blendMultiplyFloat(Cb, Cs: float32): float32 {.inline.} =
Cb * Cs Cb * Cs
proc blendLinearBurn(Cb, Cs: float32): float32 {.inline.} = proc blendLinearBurnFloat(Cb, Cs: float32): float32 {.inline.} =
Cb + Cs - 1 Cb + Cs - 1
proc blendColorBurn(Cb, Cs: float32): float32 {.inline.} = proc blendColorBurnFloat(Cb, Cs: float32): float32 {.inline.} =
if Cb == 1: 1.0 if Cb == 1: 1.0
elif Cs == 0: 0.0 elif Cs == 0: 0.0
else: 1.0 - min(1, (1 - Cb) / Cs) else: 1.0 - min(1, (1 - Cb) / Cs)
proc blendLighten(Cb, Cs: float32): float32 {.inline.} = proc blendLightenFloat(Cb, Cs: float32): float32 {.inline.} =
max(Cb, Cs) max(Cb, Cs)
proc blendScreen(Cb, Cs: float32): float32 {.inline.} = proc blendScreenFloat(Cb, Cs: float32): float32 {.inline.} =
screen(Cb, Cs) screen(Cb, Cs)
proc blendLinearDodge(Cb, Cs: float32): float32 {.inline.} = proc blendLinearDodgeFloat(Cb, Cs: float32): float32 {.inline.} =
Cb + Cs Cb + Cs
proc blendColorDodge(Cb, Cs: float32): float32 {.inline.} = proc blendColorDodgeFloat(Cb, Cs: float32): float32 {.inline.} =
if Cb == 0: 0.0 if Cb == 0: 0.0
elif Cs == 1: 1.0 elif Cs == 1: 1.0
else: min(1, Cb / (1 - Cs)) else: min(1, Cb / (1 - Cs))
proc blendOverlay(Cb, Cs: float32): float32 {.inline.} = proc blendOverlayFloat(Cb, Cs: float32): float32 {.inline.} =
hardLight(Cs, Cb) hardLight(Cs, Cb)
proc blendHardLight(Cb, Cs: float32): float32 {.inline.} = proc blendHardLightFloat(Cb, Cs: float32): float32 {.inline.} =
hardLight(Cb, Cs) hardLight(Cb, Cs)
proc blendSoftLight(Cb, Cs: float32): float32 {.inline.} = proc blendSoftLightFloat(Cb, Cs: float32): float32 {.inline.} =
softLight(Cb, Cs) softLight(Cb, Cs)
proc blendDifference(Cb, Cs: float32): float32 {.inline.} = proc blendDifferenceFloat(Cb, Cs: float32): float32 {.inline.} =
abs(Cb - Cs) abs(Cb - Cs)
proc blendExclusion(Cb, Cs: float32): float32 {.inline.} = proc blendExclusionFloat(Cb, Cs: float32): float32 {.inline.} =
Cb + Cs - 2 * Cb * Cs Cb + Cs - 2 * Cb * Cs
proc blendNormal*(Cb, Cs: Color): Color {.inline.} = proc blendNormalFloats(Cb, Cs: Color): Color {.inline.} =
result.r = Cs.r result.r = Cs.r
result.g = Cs.g result.g = Cs.g
result.b = Cs.b result.b = Cs.b
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendDarken(Cb, Cs: Color): Color {.inline.} = proc blendDarkenFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendDarken(Cb.r, Cs.r) result.r = blendDarkenFloat(Cb.r, Cs.r)
result.g = blendDarken(Cb.g, Cs.g) result.g = blendDarkenFloat(Cb.g, Cs.g)
result.b = blendDarken(Cb.b, Cs.b) result.b = blendDarkenFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendMultiply(Cb, Cs: Color): Color {.inline.} = proc blendMultiplyFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendMultiply(Cb.r, Cs.r) result.r = blendMultiplyFloat(Cb.r, Cs.r)
result.g = blendMultiply(Cb.g, Cs.g) result.g = blendMultiplyFloat(Cb.g, Cs.g)
result.b = blendMultiply(Cb.b, Cs.b) result.b = blendMultiplyFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendLinearBurn(Cb, Cs: Color): Color {.inline.} = proc blendLinearBurnFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendLinearBurn(Cb.r, Cs.r) result.r = blendLinearBurnFloat(Cb.r, Cs.r)
result.g = blendLinearBurn(Cb.g, Cs.g) result.g = blendLinearBurnFloat(Cb.g, Cs.g)
result.b = blendLinearBurn(Cb.b, Cs.b) result.b = blendLinearBurnFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendColorBurn(Cb, Cs: Color): Color {.inline.} = proc blendColorBurnFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendColorBurn(Cb.r, Cs.r) result.r = blendColorBurnFloat(Cb.r, Cs.r)
result.g = blendColorBurn(Cb.g, Cs.g) result.g = blendColorBurnFloat(Cb.g, Cs.g)
result.b = blendColorBurn(Cb.b, Cs.b) result.b = blendColorBurnFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendLighten(Cb, Cs: Color): Color {.inline.} = proc blendLightenFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendLighten(Cb.r, Cs.r) result.r = blendLightenFloat(Cb.r, Cs.r)
result.g = blendLighten(Cb.g, Cs.g) result.g = blendLightenFloat(Cb.g, Cs.g)
result.b = blendLighten(Cb.b, Cs.b) result.b = blendLightenFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendScreen(Cb, Cs: Color): Color {.inline.} = proc blendScreenFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendScreen(Cb.r, Cs.r) result.r = blendScreenFloat(Cb.r, Cs.r)
result.g = blendScreen(Cb.g, Cs.g) result.g = blendScreenFloat(Cb.g, Cs.g)
result.b = blendScreen(Cb.b, Cs.b) result.b = blendScreenFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendLinearDodge(Cb, Cs: Color): Color {.inline.} = proc blendLinearDodgeFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendLinearDodge(Cb.r, Cs.r) result.r = blendLinearDodgeFloat(Cb.r, Cs.r)
result.g = blendLinearDodge(Cb.g, Cs.g) result.g = blendLinearDodgeFloat(Cb.g, Cs.g)
result.b = blendLinearDodge(Cb.b, Cs.b) result.b = blendLinearDodgeFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendColorDodge(Cb, Cs: Color): Color {.inline.} = proc blendColorDodgeFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendColorDodge(Cb.r, Cs.r) result.r = blendColorDodgeFloat(Cb.r, Cs.r)
result.g = blendColorDodge(Cb.g, Cs.g) result.g = blendColorDodgeFloat(Cb.g, Cs.g)
result.b = blendColorDodge(Cb.b, Cs.b) result.b = blendColorDodgeFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendOverlay(Cb, Cs: Color): Color {.inline.} = proc blendOverlayFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendOverlay(Cb.r, Cs.r) result.r = blendOverlayFloat(Cb.r, Cs.r)
result.g = blendOverlay(Cb.g, Cs.g) result.g = blendOverlayFloat(Cb.g, Cs.g)
result.b = blendOverlay(Cb.b, Cs.b) result.b = blendOverlayFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendHardLight(Cb, Cs: Color): Color {.inline.} = proc blendHardLightFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendHardLight(Cb.r, Cs.r) result.r = blendHardLightFloat(Cb.r, Cs.r)
result.g = blendHardLight(Cb.g, Cs.g) result.g = blendHardLightFloat(Cb.g, Cs.g)
result.b = blendHardLight(Cb.b, Cs.b) result.b = blendHardLightFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendSoftLight(Cb, Cs: Color): Color {.inline.} = proc blendSoftLightFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendSoftLight(Cb.r, Cs.r) result.r = blendSoftLightFloat(Cb.r, Cs.r)
result.g = blendSoftLight(Cb.g, Cs.g) result.g = blendSoftLightFloat(Cb.g, Cs.g)
result.b = blendSoftLight(Cb.b, Cs.b) result.b = blendSoftLightFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendDifference(Cb, Cs: Color): Color {.inline.} = proc blendDifferenceFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendDifference(Cb.r, Cs.r) result.r = blendDifferenceFloat(Cb.r, Cs.r)
result.g = blendDifference(Cb.g, Cs.g) result.g = blendDifferenceFloat(Cb.g, Cs.g)
result.b = blendDifference(Cb.b, Cs.b) result.b = blendDifferenceFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendExclusion(Cb, Cs: Color): Color {.inline.} = proc blendExclusionFloats(Cb, Cs: Color): Color {.inline.} =
result.r = blendExclusion(Cb.r, Cs.r) result.r = blendExclusionFloat(Cb.r, Cs.r)
result.g = blendExclusion(Cb.g, Cs.g) result.g = blendExclusionFloat(Cb.g, Cs.g)
result.b = blendExclusion(Cb.b, Cs.b) result.b = blendExclusionFloat(Cb.b, Cs.b)
result = alphaFix(Cb, Cs, result) result = alphaFix(Cb, Cs, result)
proc blendColor(Cb, Cs: Color): Color {.inline.} = proc blendColorFloats(Cb, Cs: Color): Color {.inline.} =
let mixed = SetLum(Cs, Lum(Cb)) let mixed = SetLum(Cs, Lum(Cb))
alphaFix(Cb, Cs, mixed) alphaFix(Cb, Cs, mixed)
proc blendLuminosity(Cb, Cs: Color): Color {.inline.} = proc blendLuminosityFloats(Cb, Cs: Color): Color {.inline.} =
let mixed = SetLum(Cb, Lum(Cs)) let mixed = SetLum(Cb, Lum(Cs))
alphaFix(Cb, Cs, mixed) alphaFix(Cb, Cs, mixed)
proc blendHue(Cb, Cs: Color): Color {.inline.} = proc blendHueFloats(Cb, Cs: Color): Color {.inline.} =
let mixed = SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb)) let mixed = SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb))
alphaFix(Cb, Cs, mixed) alphaFix(Cb, Cs, mixed)
proc blendSaturation(Cb, Cs: Color): Color {.inline.} = proc blendSaturationFloats(Cb, Cs: Color): Color {.inline.} =
let mixed = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb)) let mixed = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))
alphaFix(Cb, Cs, mixed) alphaFix(Cb, Cs, mixed)
proc blendMask(target, blend: Color): Color {.inline.} = proc blendMaskFloats(target, blend: Color): Color {.inline.} =
result.r = target.r result.r = target.r
result.g = target.g result.g = target.g
result.b = target.b result.b = target.b
result.a = min(target.a, blend.a) result.a = min(target.a, blend.a)
proc blendSubtractMask(target, blend: Color): Color {.inline.} = proc blendSubtractMaskFloats(target, blend: Color): Color {.inline.} =
result.r = target.r result.r = target.r
result.g = target.g result.g = target.g
result.b = target.b result.b = target.b
result.a = target.a * (1 - blend.a) result.a = target.a * (1 - blend.a)
proc blendIntersectMask(target, blend: Color): Color {.inline.} = proc blendIntersectMaskFloats(target, blend: Color): Color {.inline.} =
result.r = target.r result.r = target.r
result.g = target.g result.g = target.g
result.b = target.b result.b = target.b
result.a = target.a * blend.a result.a = target.a * blend.a
proc blendExcludeMask(target, blend: Color): Color {.inline.} = proc blendExcludeMaskFloats(target, blend: Color): Color {.inline.} =
result.r = target.r result.r = target.r
result.g = target.g result.g = target.g
result.b = target.b result.b = target.b
result.a = abs(target.a - blend.a) result.a = abs(target.a - blend.a)
proc blendOverwrite(target, blend: Color): Color {.inline.} = proc blendOverwriteFloats(target, blend: Color): Color {.inline.} =
result = blend result = blend
proc mix*(blendMode: BlendMode, dest, src: Color): Color {.inline.} =
case blendMode
of bmNormal: blendNormal(dest, src)
of bmDarken: blendDarken(dest, src)
of bmMultiply: blendMultiply(dest, src)
of bmLinearBurn: blendLinearBurn(dest, src)
of bmColorBurn: blendColorBurn(dest, src)
of bmLighten: blendLighten(dest, src)
of bmScreen: blendScreen(dest, src)
of bmLinearDodge: blendLinearDodge(dest, src)
of bmColorDodge: blendColorDodge(dest, src)
of bmOverlay: blendOverlay(dest, src)
of bmSoftLight: blendSoftLight(dest, src)
of bmHardLight: blendHardLight(dest, src)
of bmDifference: blendDifference(dest, src)
of bmExclusion: blendExclusion(dest, src)
of bmHue: blendHue(dest, src)
of bmSaturation: blendSaturation(dest, src)
of bmColor: blendColor(dest, src)
of bmLuminosity: blendLuminosity(dest, src)
of bmMask: blendMask(dest, src)
of bmOverwrite: blendOverwrite(dest, src)
of bmSubtractMask: blendSubtractMask(dest, src)
of bmIntersectMask: blendIntersectMask(dest, src)
of bmExcludeMask: blendExcludeMask(dest, src)
proc alphaFix(Cb, Cs, mixed: ColorRGBA): ColorRGBA {.inline.} = proc alphaFix(Cb, Cs, mixed: ColorRGBA): ColorRGBA {.inline.} =
let ab = Cb.a.int32 let ab = Cb.a.int32
let As = Cs.a.int32 let As = Cs.a.int32
@ -335,129 +312,103 @@ proc alphaFix(Cb, Cs, mixed: ColorRGBA): ColorRGBA {.inline.} =
result.b = (b div a div 255).uint8 result.b = (b div a div 255).uint8
result.a = a.uint8 result.a = a.uint8
proc blendNormal*(a, b: ColorRGBA): ColorRGBA = proc blendNormal(a, b: ColorRGBA): ColorRGBA =
# blendNormal(a.color, b.color).rgba
result.r = b.r result.r = b.r
result.g = b.g result.g = b.g
result.b = b.b result.b = b.b
result = alphaFix(a, b, result) result = alphaFix(a, b, result)
proc blendDarken*(a, b: ColorRGBA): ColorRGBA = proc blendDarken(a, b: ColorRGBA): ColorRGBA =
blendDarken(a.color, b.color).rgba blendDarkenFloats(a.color, b.color).rgba
proc blendMultiply*(a, b: ColorRGBA): ColorRGBA = proc blendMultiply(a, b: ColorRGBA): ColorRGBA =
blendMultiply(a.color, b.color).rgba blendMultiplyFloats(a.color, b.color).rgba
proc blendLinearBurn*(a, b: ColorRGBA): ColorRGBA = proc blendLinearBurn(a, b: ColorRGBA): ColorRGBA =
blendLinearBurn(a.color, b.color).rgba blendLinearBurnFloats(a.color, b.color).rgba
proc blendColorBurn*(a, b: ColorRGBA): ColorRGBA = proc blendColorBurn(a, b: ColorRGBA): ColorRGBA =
blendColorBurn(a.color, b.color).rgba blendColorBurnFloats(a.color, b.color).rgba
proc blendLighten*(a, b: ColorRGBA): ColorRGBA = proc blendLighten(a, b: ColorRGBA): ColorRGBA =
blendLighten(a.color, b.color).rgba blendLightenFloats(a.color, b.color).rgba
proc blendScreen*(a, b: ColorRGBA): ColorRGBA = proc blendScreen(a, b: ColorRGBA): ColorRGBA =
blendScreen(a.color, b.color).rgba blendScreenFloats(a.color, b.color).rgba
proc blendLinearDodge*(a, b: ColorRGBA): ColorRGBA = proc blendLinearDodge(a, b: ColorRGBA): ColorRGBA =
blendLinearDodge(a.color, b.color).rgba blendLinearDodgeFloats(a.color, b.color).rgba
proc blendColorDodge*(a, b: ColorRGBA): ColorRGBA = proc blendColorDodge(a, b: ColorRGBA): ColorRGBA =
blendColorDodge(a.color, b.color).rgba blendColorDodgeFloats(a.color, b.color).rgba
proc blendOverlay*(a, b: ColorRGBA): ColorRGBA = proc blendOverlay(a, b: ColorRGBA): ColorRGBA =
blendOverlay(a.color, b.color).rgba blendOverlayFloats(a.color, b.color).rgba
proc blendHardLight*(a, b: ColorRGBA): ColorRGBA = proc blendHardLight(a, b: ColorRGBA): ColorRGBA =
blendHardLight(a.color, b.color).rgba blendHardLightFloats(a.color, b.color).rgba
proc blendSoftLight*(a, b: ColorRGBA): ColorRGBA = proc blendSoftLight(a, b: ColorRGBA): ColorRGBA =
blendSoftLight(a.color, b.color).rgba blendSoftLightFloats(a.color, b.color).rgba
proc blendDifference*(a, b: ColorRGBA): ColorRGBA = proc blendDifference(a, b: ColorRGBA): ColorRGBA =
blendDifference(a.color, b.color).rgba blendDifferenceFloats(a.color, b.color).rgba
proc blendExclusion*(a, b: ColorRGBA): ColorRGBA = proc blendExclusion(a, b: ColorRGBA): ColorRGBA =
blendExclusion(a.color, b.color).rgba blendExclusionFloats(a.color, b.color).rgba
proc blendColor*(a, b: ColorRGBA): ColorRGBA = proc blendColor(a, b: ColorRGBA): ColorRGBA =
blendColor(a.color, b.color).rgba blendColorFloats(a.color, b.color).rgba
proc blendLuminosity*(a, b: ColorRGBA): ColorRGBA = proc blendLuminosity(a, b: ColorRGBA): ColorRGBA =
blendLuminosity(a.color, b.color).rgba blendLuminosityFloats(a.color, b.color).rgba
proc blendHue*(a, b: ColorRGBA): ColorRGBA = proc blendHue(a, b: ColorRGBA): ColorRGBA =
blendHue(a.color, b.color).rgba blendHueFloats(a.color, b.color).rgba
proc blendSaturation*(a, b: ColorRGBA): ColorRGBA = proc blendSaturation(a, b: ColorRGBA): ColorRGBA =
blendSaturation(a.color, b.color).rgba blendSaturationFloats(a.color, b.color).rgba
proc blendMask*(a, b: ColorRGBA): ColorRGBA = proc blendMask(a, b: ColorRGBA): ColorRGBA =
result.r = a.r result.r = a.r
result.g = a.g result.g = a.g
result.b = a.b result.b = a.b
result.a = min(a.a, b.a) result.a = min(a.a, b.a)
proc blendSubtractMask*(a, b: ColorRGBA): ColorRGBA = proc blendSubtractMask(a, b: ColorRGBA): ColorRGBA =
blendSubtractMask(a.color, b.color).rgba blendSubtractMaskFloats(a.color, b.color).rgba
proc blendIntersectMask*(a, b: ColorRGBA): ColorRGBA = proc blendIntersectMask(a, b: ColorRGBA): ColorRGBA =
blendIntersectMask(a.color, b.color).rgba blendIntersectMaskFloats(a.color, b.color).rgba
proc blendExcludeMask*(a, b: ColorRGBA): ColorRGBA = proc blendExcludeMask(a, b: ColorRGBA): ColorRGBA =
blendExcludeMask(a.color, b.color).rgba blendExcludeMaskFloats(a.color, b.color).rgba
proc blendOverwrite*(a, b: ColorRGBA): ColorRGBA = proc blendOverwrite(a, b: ColorRGBA): ColorRGBA =
blendOverwrite(a.color, b.color).rgba blendOverwriteFloats(a.color, b.color).rgba
proc mix*(blendMode: BlendMode, dest, src: ColorRGBA): ColorRGBA {.inline.} = proc mixer*(blendMode: BlendMode): Mixer =
case blendMode case blendMode
of bmNormal: blendNormal(dest, src) of bmNormal: blendNormal
of bmDarken: blendDarken(dest, src) of bmDarken: blendDarken
of bmMultiply: blendMultiply(dest, src) of bmMultiply: blendMultiply
of bmLinearBurn: blendLinearBurn(dest, src) of bmLinearBurn: blendLinearBurn
of bmColorBurn: blendColorBurn(dest, src) of bmColorBurn: blendColorBurn
of bmLighten: blendLighten(dest, src) of bmLighten: blendLighten
of bmScreen: blendScreen(dest, src) of bmScreen: blendScreen
of bmLinearDodge: blendLinearDodge(dest, src) of bmLinearDodge: blendLinearDodge
of bmColorDodge: blendColorDodge(dest, src) of bmColorDodge: blendColorDodge
of bmOverlay: blendOverlay(dest, src) of bmOverlay: blendOverlay
of bmSoftLight: blendSoftLight(dest, src) of bmSoftLight: blendSoftLight
of bmHardLight: blendHardLight(dest, src) of bmHardLight: blendHardLight
of bmDifference: blendDifference(dest, src) of bmDifference: blendDifference
of bmExclusion: blendExclusion(dest, src) of bmExclusion: blendExclusion
of bmHue: blendHue(dest, src) of bmHue: blendHue
of bmSaturation: blendSaturation(dest, src) of bmSaturation: blendSaturation
of bmColor: blendColor(dest, src) of bmColor: blendColor
of bmLuminosity: blendLuminosity(dest, src) of bmLuminosity: blendLuminosity
of bmMask: blendMask(dest, src) of bmMask: blendMask
of bmOverwrite: blendOverwrite(dest, src) of bmOverwrite: blendOverwrite
of bmSubtractMask: blendSubtractMask(dest, src) of bmSubtractMask: blendSubtractMask
of bmIntersectMask: blendIntersectMask(dest, src) of bmIntersectMask: blendIntersectMask
of bmExcludeMask: blendExcludeMask(dest, src) of bmExcludeMask: blendExcludeMask
proc mixStatic*(blendMode: static[BlendMode], dest, src: ColorRGBA): ColorRGBA {.inline.} =
when blendMOde == bmNormal: blendNormal(dest, src)
elif blendMOde == bmDarken: blendDarken(dest, src)
elif blendMOde == bmMultiply: blendMultiply(dest, src)
elif blendMOde == bmLinearBurn: blendLinearBurn(dest, src)
elif blendMOde == bmColorBurn: blendColorBurn(dest, src)
elif blendMOde == bmLighten: blendLighten(dest, src)
elif blendMOde == bmScreen: blendScreen(dest, src)
elif blendMOde == bmLinearDodge: blendLinearDodge(dest, src)
elif blendMOde == bmColorDodge: blendColorDodge(dest, src)
elif blendMOde == bmOverlay: blendOverlay(dest, src)
elif blendMOde == bmSoftLight: blendSoftLight(dest, src)
elif blendMOde == bmHardLight: blendHardLight(dest, src)
elif blendMOde == bmDifference: blendDifference(dest, src)
elif blendMOde == bmExclusion: blendExclusion(dest, src)
elif blendMOde == bmHue: blendHue(dest, src)
elif blendMOde == bmSaturation: blendSaturation(dest, src)
elif blendMOde == bmColor: blendColor(dest, src)
elif blendMOde == bmLuminosity: blendLuminosity(dest, src)
elif blendMOde == bmMask: blendMask(dest, src)
elif blendMOde == bmOverwrite: blendOverwrite(dest, src)
elif blendMOde == bmSubtractMask: blendSubtractMask(dest, src)
elif blendMOde == bmIntersectMask: blendIntersectMask(dest, src)
elif blendMOde == bmExcludeMask: blendExcludeMask(dest, src)

View file

@ -1,4 +1,4 @@
import chroma, blends, vmath, common, system/memory import chroma, blends, vmath, common, nimsimd/sse2
type type
Image* = ref object Image* = ref object
@ -70,19 +70,31 @@ proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} =
proc fill*(image: Image, rgba: ColorRgba) = proc fill*(image: Image, rgba: ColorRgba) =
## Fills the image with a solid color. ## Fills the image with a solid color.
if rgba.r == rgba.g and rgba.r == rgba.b and rgba.r == rgba.a: # SIMD fill until we run out of room.
nimSetMem(image.data[0].addr, rgba.r.cint, image.data.len * 4) let m = mm_set1_epi32(cast[int32](rgba))
else: var i: int
for c in image.data.mitems: while i < image.data.len - 4:
c = rgba mm_store_si128(image.data[i].addr, m)
i += 4
for j in i ..< image.data.len:
image.data[j] = rgba
proc invert*(image: Image) = proc invert*(image: Image) =
## Inverts all of the colors and alpha. ## Inverts all of the colors and alpha.
for rgba in image.data.mitems: let vec255 = mm_set1_epi8(255)
var i: int
while i < image.data.len - 4:
var m = mm_loadu_si128(image.data[i].addr)
m = mm_sub_epi8(vec255, m)
mm_store_si128(image.data[i].addr, m)
i += 4
for j in i ..< image.data.len:
var rgba = image.data[j]
rgba.r = 255 - rgba.r rgba.r = 255 - rgba.r
rgba.g = 255 - rgba.g rgba.g = 255 - rgba.g
rgba.b = 255 - rgba.b rgba.b = 255 - rgba.b
rgba.a = 255 - rgba.a rgba.a = 255 - rgba.a
image.data[j] = rgba
proc subImage*(image: Image, x, y, w, h: int): Image = proc subImage*(image: Image, x, y, w, h: int): Image =
## Gets a sub image of the main image. ## Gets a sub image of the main image.
@ -394,13 +406,14 @@ proc drawCorrect*(a, b: Image, mat: Mat3, blendMode: BlendMode) =
minFilterBy2 /= 2 minFilterBy2 /= 2
matInv = matInv * scale(vec2(0.5, 0.5)) matInv = matInv * scale(vec2(0.5, 0.5))
let mixer = blendMode.mixer()
for y in 0 ..< a.height: for y in 0 ..< a.height:
for x in 0 ..< a.width: for x in 0 ..< a.width:
let srcPos = matInv * vec2(x.float32 + h, y.float32 + h) let
var rgba = a.getRgbaUnsafe(x, y) srcPos = matInv * vec2(x.float32 + h, y.float32 + h)
let rgba2 = b.getRgbaSmooth(srcPos.x - h, srcPos.y - h) rgba = a.getRgbaUnsafe(x, y)
rgba = blendMode.mix(rgba, rgba2) rgba2 = b.getRgbaSmooth(srcPos.x - h, srcPos.y - h)
a.setRgbaUnsafe(x, y, rgba) a.setRgbaUnsafe(x, y, mixer(rgba, rgba2))
const h = 0.5.float32 const h = 0.5.float32
@ -408,9 +421,10 @@ proc drawUber(
a, b: Image, a, b: Image,
p, dx, dy: Vec2, p, dx, dy: Vec2,
lines: array[0..3, Segment], lines: array[0..3, Segment],
blendMode: static[BlendMode], blendMode: BlendMode,
smooth: static[bool] smooth: bool
) = ) =
let mixer = blendMode.mixer()
for y in 0 ..< a.height: for y in 0 ..< a.height:
var var
xMin = 0 xMin = 0
@ -435,25 +449,24 @@ proc drawUber(
xMin = xMin.clamp(0, a.width) xMin = xMin.clamp(0, a.width)
xMax = xMax.clamp(0, a.width) xMax = xMax.clamp(0, a.width)
when blendMode == bmIntersectMask: if blendMode == bmIntersectMask:
if xMin > 0: if xMin > 0:
zeroMem(a.getAddr(0, y), 4 * xMin) zeroMem(a.getAddr(0, y), 4 * xMin)
for x in xMin ..< xMax: for x in xMin ..< xMax:
let srcPos = p + dx * float32(x) + dy * float32(y)
let let
srcPos = p + dx * float32(x) + dy * float32(y)
xFloat = srcPos.x - h xFloat = srcPos.x - h
yFloat = srcPos.y - h yFloat = srcPos.y - h
var rgba = a.getRgbaUnsafe(x, y) rgba = a.getRgbaUnsafe(x, y)
var rgba2 = rgba2 =
when smooth: if smooth:
b.getRgbaSmooth(xFloat, yFloat) b.getRgbaSmooth(xFloat, yFloat)
else: else:
b.getRgbaUnsafe(xFloat.round.int, yFloat.round.int) b.getRgbaUnsafe(xFloat.round.int, yFloat.round.int)
rgba = blendMode.mixStatic(rgba, rgba2) a.setRgbaUnsafe(x, y, mixer(rgba, rgba2))
a.setRgbaUnsafe(x, y, rgba)
when blendMode == bmIntersectMask: if blendMode == bmIntersectMask:
if a.width - xMax > 0: if a.width - xMax > 0:
zeroMem(a.getAddr(xMax, y), 4 * (a.width - xMax)) zeroMem(a.getAddr(xMax, y), 4 * (a.width - xMax))
@ -494,56 +507,7 @@ proc draw*(a, b: Image, mat: Mat3, blendMode: BlendMode) =
let smooth = not(dx.length == 1.0 and dy.length == 1.0 and 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) mat[2, 0].fractional == 0.0 and mat[2, 1].fractional == 0.0)
if not smooth: a.drawUber(b, p, dx, dy, lines, blendMode, smooth)
case blendMode
of bmNormal: a.drawUber(b, p, dx, dy, lines, bmNormal, false)
of bmDarken: a.drawUber(b, p, dx, dy, lines, bmDarken, false)
of bmMultiply: a.drawUber(b, p, dx, dy, lines, bmMultiply, false)
of bmLinearBurn: a.drawUber(b, p, dx, dy, lines, bmLinearBurn, false)
of bmColorBurn: a.drawUber(b, p, dx, dy, lines, bmColorBurn, false)
of bmLighten: a.drawUber(b, p, dx, dy, lines, bmLighten, false)
of bmScreen: a.drawUber(b, p, dx, dy, lines, bmScreen, false)
of bmLinearDodge: a.drawUber(b, p, dx, dy, lines, bmLinearDodge, false)
of bmColorDodge: a.drawUber(b, p, dx, dy, lines, bmColorDodge, false)
of bmOverlay: a.drawUber(b, p, dx, dy, lines, bmOverlay, false)
of bmSoftLight: a.drawUber(b, p, dx, dy, lines, bmSoftLight, false)
of bmHardLight: a.drawUber(b, p, dx, dy, lines, bmHardLight, false)
of bmDifference: a.drawUber(b, p, dx, dy, lines, bmDifference, false)
of bmExclusion: a.drawUber(b, p, dx, dy, lines, bmExclusion, false)
of bmHue: a.drawUber(b, p, dx, dy, lines, bmHue, false)
of bmSaturation: a.drawUber(b, p, dx, dy, lines, bmSaturation, false)
of bmColor: a.drawUber(b, p, dx, dy, lines, bmColor, false)
of bmLuminosity: a.drawUber(b, p, dx, dy, lines, bmLuminosity, false)
of bmMask: a.drawUber(b, p, dx, dy, lines, bmMask, false)
of bmOverwrite: a.drawUber(b, p, dx, dy, lines, bmOverwrite, false)
of bmSubtractMask: a.drawUber(b, p, dx, dy, lines, bmSubtractMask, false)
of bmIntersectMask: a.drawUber(b, p, dx, dy, lines, bmIntersectMask, false)
of bmExcludeMask: a.drawUber(b, p, dx, dy, lines, bmExcludeMask, false)
else:
case blendMode
of bmNormal: a.drawUber(b, p, dx, dy, lines, bmNormal, true)
of bmDarken: a.drawUber(b, p, dx, dy, lines, bmDarken, true)
of bmMultiply: a.drawUber(b, p, dx, dy, lines, bmMultiply, true)
of bmLinearBurn: a.drawUber(b, p, dx, dy, lines, bmLinearBurn, true)
of bmColorBurn: a.drawUber(b, p, dx, dy, lines, bmColorBurn, true)
of bmLighten: a.drawUber(b, p, dx, dy, lines, bmLighten, true)
of bmScreen: a.drawUber(b, p, dx, dy, lines, bmScreen, true)
of bmLinearDodge: a.drawUber(b, p, dx, dy, lines, bmLinearDodge, true)
of bmColorDodge: a.drawUber(b, p, dx, dy, lines, bmColorDodge, true)
of bmOverlay: a.drawUber(b, p, dx, dy, lines, bmOverlay, true)
of bmSoftLight: a.drawUber(b, p, dx, dy, lines, bmSoftLight, true)
of bmHardLight: a.drawUber(b, p, dx, dy, lines, bmHardLight, true)
of bmDifference: a.drawUber(b, p, dx, dy, lines, bmDifference, true)
of bmExclusion: a.drawUber(b, p, dx, dy, lines, bmExclusion, true)
of bmHue: a.drawUber(b, p, dx, dy, lines, bmHue, true)
of bmSaturation: a.drawUber(b, p, dx, dy, lines, bmSaturation, true)
of bmColor: a.drawUber(b, p, dx, dy, lines, bmColor, true)
of bmLuminosity: a.drawUber(b, p, dx, dy, lines, bmLuminosity, true)
of bmMask: a.drawUber(b, p, dx, dy, lines, bmMask, true)
of bmOverwrite: a.drawUber(b, p, dx, dy, lines, bmOverwrite, true)
of bmSubtractMask: a.drawUber(b, p, dx, dy, lines, bmSubtractMask, true)
of bmIntersectMask: a.drawUber(b, p, dx, dy, lines, bmIntersectMask, true)
of bmExcludeMask: a.drawUber(b, p, dx, dy, lines, bmExcludeMask, true)
proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} = proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} =
a.draw(b, translate(pos), blendMode) a.draw(b, translate(pos), blendMode)

View file

@ -1,11 +1,19 @@
import chroma, pixie, benchy import chroma, pixie, benchy, system/memory
proc fillOriginal(a: Image, rgba: ColorRGBA) = proc fill1(a: Image, rgba: ColorRGBA) =
for y in 0 ..< a.height: for y in 0 ..< a.height:
for x in 0 ..< a.width: for x in 0 ..< a.width:
a.setRgbaUnsafe(x, y, rgba) a.setRgbaUnsafe(x, y, rgba)
proc invertOriginal(a: Image) = proc fill2*(image: Image, rgba: ColorRgba) =
## Fills the image with a solid color.
if rgba.r == rgba.g and rgba.r == rgba.b and rgba.r == rgba.a:
nimSetMem(image.data[0].addr, rgba.r.cint, image.data.len * 4)
else:
for c in image.data.mitems:
c = rgba
proc invert1(a: Image) =
for y in 0 ..< a.height: for y in 0 ..< a.height:
for x in 0 ..< a.width: for x in 0 ..< a.width:
var rgba = a.getRgbaUnsafe(x, y) var rgba = a.getRgbaUnsafe(x, y)
@ -15,7 +23,14 @@ proc invertOriginal(a: Image) =
rgba.a = 255 - rgba.a rgba.a = 255 - rgba.a
a.setRgbaUnsafe(x, y, rgba) a.setRgbaUnsafe(x, y, rgba)
proc applyOpacityOriginal(a: Image, opacity: float32): Image = proc invert2*(image: Image) =
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 applyOpacity1(a: Image, opacity: float32): Image =
result = newImage(a.width, a.height) result = newImage(a.width, a.height)
let op = (255 * opacity).uint32 let op = (255 * opacity).uint32
for y in 0 ..< a.height: for y in 0 ..< a.height:
@ -24,7 +39,7 @@ proc applyOpacityOriginal(a: Image, opacity: float32): Image =
rgba.a = ((rgba.a.uint32 * op) div 255).clamp(0, 255).uint8 rgba.a = ((rgba.a.uint32 * op) div 255).clamp(0, 255).uint8
result.setRgbaUnsafe(x, y, rgba) result.setRgbaUnsafe(x, y, rgba)
proc sharpOpacityOriginal(a: Image): Image = proc sharpOpacity1(a: Image): Image =
result = newImage(a.width, a.height) result = newImage(a.width, a.height)
for y in 0 ..< a.height: for y in 0 ..< a.height:
for x in 0 ..< a.width: for x in 0 ..< a.width:
@ -36,8 +51,13 @@ proc sharpOpacityOriginal(a: Image): Image =
var a = newImage(2560, 1440) var a = newImage(2560, 1440)
timeIt "fillOriginal": timeIt "fill1":
a.fillOriginal(rgba(255, 255, 255, 255)) a.fill1(rgba(255, 255, 255, 255))
doAssert a[0, 0] == rgba(255, 255, 255, 255)
keep(a)
timeIt "fill2":
a.fill2(rgba(255, 255, 255, 255))
doAssert a[0, 0] == rgba(255, 255, 255, 255) doAssert a[0, 0] == rgba(255, 255, 255, 255)
keep(a) keep(a)
@ -46,24 +66,43 @@ timeIt "fill":
doAssert a[0, 0] == rgba(255, 255, 255, 255) doAssert a[0, 0] == rgba(255, 255, 255, 255)
keep(a) keep(a)
timeIt "invertOriginal": timeIt "fill1_rgba":
a.invertOriginal() a.fill1(rgba(63, 127, 191, 255))
doAssert a[0, 0] == rgba(63, 127, 191, 255)
keep(a)
timeIt "fill2_rgba":
a.fill2(rgba(63, 127, 191, 255))
doAssert a[0, 0] == rgba(63, 127, 191, 255)
keep(a)
timeIt "fill_rgba":
a.fill(rgba(63, 127, 191, 255))
doAssert a[0, 0] == rgba(63, 127, 191, 255)
keep(a)
timeIt "invert1":
a.invert1()
keep(a)
timeIt "invert2":
a.invert2()
keep(a) keep(a)
timeIt "invert": timeIt "invert":
a.invert() a.invert()
keep(a) keep(a)
timeIt "applyOpacityOriginal": timeIt "applyOpacity1":
a = a.applyOpacityOriginal(0.5) a = a.applyOpacity1(0.5)
keep(a) keep(a)
timeIt "applyOpacity": timeIt "applyOpacity":
a.applyOpacity(0.5) a.applyOpacity(0.5)
keep(a) keep(a)
timeIt "sharpOpacityOriginal": timeIt "sharpOpacity1":
a = a.sharpOpacityOriginal() a = a.sharpOpacity1()
keep(a) keep(a)
timeIt "sharpOpacity": timeIt "sharpOpacity":

View file

@ -4,7 +4,7 @@ block:
var c: Image var c: Image
var a = newImage(1000, 1000) var a = newImage(1000, 1000)
a.fill(rgba(255, 0, 0, 255)) a.fill(rgba(255, 0, 0, 255))
var b = newImage(1000, 1000) var b = newImage(500, 500)
b.fill(rgba(0, 255, 0, 255)) b.fill(rgba(0, 255, 0, 255))
timeIt "drawCorrect bmNormal": timeIt "drawCorrect bmNormal":
@ -14,7 +14,7 @@ block:
block: block:
var a = newImage(1000, 1000) var a = newImage(1000, 1000)
a.fill(rgba(255, 0, 0, 255)) a.fill(rgba(255, 0, 0, 255))
var b = newImage(1000, 1000) var b = newImage(500, 500)
b.fill(rgba(0, 255, 0, 255)) b.fill(rgba(0, 255, 0, 255))
timeIt "draw bmNormal": timeIt "draw bmNormal":
@ -24,7 +24,7 @@ block:
block: block:
var a = newImage(1000, 1000) var a = newImage(1000, 1000)
a.fill(rgba(255, 0, 0, 255)) a.fill(rgba(255, 0, 0, 255))
var b = newImage(1000, 1000) var b = newImage(500, 500)
b.fill(rgba(0, 255, 0, 255)) b.fill(rgba(0, 255, 0, 255))
timeIt "draw Smooth bmNormal": timeIt "draw Smooth bmNormal":

View file

@ -17,10 +17,10 @@ for channels in 1 .. 4:
var data: seq[uint8] var data: seq[uint8]
for x in 0 ..< 16: for x in 0 ..< 16:
for y in 0 ..< 16: for y in 0 ..< 16:
var components = newSeq[uint8](channels) var components = newSeq[uint8](channels)
for i in 0 ..< channels: for i in 0 ..< channels:
components[i] = (x * 16).uint8 components[i] = (x * 16).uint8
data.add(components) data.add(components)
let encoded = encodePng(16, 16, channels, data[0].addr, data.len) let encoded = encodePng(16, 16, channels, data[0].addr, data.len)
for file in pngSuiteCorruptedFiles: for file in pngSuiteCorruptedFiles: