diff --git a/pixie.nimble b/pixie.nimble index c759ac4..90a726d 100644 --- a/pixie.nimble +++ b/pixie.nimble @@ -10,3 +10,4 @@ requires "vmath >= 0.3.3" requires "chroma >= 0.1.5" requires "zippy >= 0.3.5" requires "flatty >= 0.1.2" +requires "nimsimd >= 0.4.6" diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 937c231..3b02119 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -1,34 +1,37 @@ ## Blending modes. -import chroma, math, algorithm +import chroma, math # See https://www.w3.org/TR/compositing-1/ # 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 +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 that does not blend but copies the pixels from target. - bmSubtractMask ## Inverse mask - bmIntersectMask - bmExcludeMask + bmMask ## Special blend mode that is used for masking + bmOverwrite ## Special that does not blend but copies the pixels from target. + bmSubtractMask ## Inverse mask + bmIntersectMask + bmExcludeMask + + Mixer* = proc(a, b: ColorRGBA): ColorRGBA proc `+`*(a, b: Color): Color {.inline.} = result.r = a.r + b.r @@ -123,202 +126,176 @@ proc alphaFix(Cb, Cs, mixed: Color): Color {.inline.} = result.g /= result.a result.b /= result.a -proc blendDarken(Cb, Cs: float32): float32 {.inline.} = +proc blendDarkenFloat(Cb, Cs: float32): float32 {.inline.} = min(Cb, Cs) -proc blendMultiply(Cb, Cs: float32): float32 {.inline.} = +proc blendMultiplyFloat(Cb, Cs: float32): float32 {.inline.} = Cb * Cs -proc blendLinearBurn(Cb, Cs: float32): float32 {.inline.} = +proc blendLinearBurnFloat(Cb, Cs: float32): float32 {.inline.} = Cb + Cs - 1 -proc blendColorBurn(Cb, Cs: float32): float32 {.inline.} = +proc blendColorBurnFloat(Cb, Cs: float32): float32 {.inline.} = if Cb == 1: 1.0 elif Cs == 0: 0.0 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) -proc blendScreen(Cb, Cs: float32): float32 {.inline.} = +proc blendScreenFloat(Cb, Cs: float32): float32 {.inline.} = screen(Cb, Cs) -proc blendLinearDodge(Cb, Cs: float32): float32 {.inline.} = +proc blendLinearDodgeFloat(Cb, Cs: float32): float32 {.inline.} = Cb + Cs -proc blendColorDodge(Cb, Cs: float32): float32 {.inline.} = +proc blendColorDodgeFloat(Cb, Cs: float32): float32 {.inline.} = if Cb == 0: 0.0 elif Cs == 1: 1.0 else: min(1, Cb / (1 - Cs)) -proc blendOverlay(Cb, Cs: float32): float32 {.inline.} = +proc blendOverlayFloat(Cb, Cs: float32): float32 {.inline.} = hardLight(Cs, Cb) -proc blendHardLight(Cb, Cs: float32): float32 {.inline.} = +proc blendHardLightFloat(Cb, Cs: float32): float32 {.inline.} = hardLight(Cb, Cs) -proc blendSoftLight(Cb, Cs: float32): float32 {.inline.} = +proc blendSoftLightFloat(Cb, Cs: float32): float32 {.inline.} = softLight(Cb, Cs) -proc blendDifference(Cb, Cs: float32): float32 {.inline.} = +proc blendDifferenceFloat(Cb, Cs: float32): float32 {.inline.} = abs(Cb - Cs) -proc blendExclusion(Cb, Cs: float32): float32 {.inline.} = +proc blendExclusionFloat(Cb, Cs: float32): float32 {.inline.} = Cb + Cs - 2 * Cb * Cs -proc blendNormal*(Cb, Cs: Color): Color {.inline.} = +proc blendNormalFloats(Cb, Cs: Color): Color {.inline.} = result.r = Cs.r result.g = Cs.g result.b = Cs.b result = alphaFix(Cb, Cs, result) -proc blendDarken(Cb, Cs: Color): Color {.inline.} = - result.r = blendDarken(Cb.r, Cs.r) - result.g = blendDarken(Cb.g, Cs.g) - result.b = blendDarken(Cb.b, Cs.b) +proc blendDarkenFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendDarkenFloat(Cb.r, Cs.r) + result.g = blendDarkenFloat(Cb.g, Cs.g) + result.b = blendDarkenFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendMultiply(Cb, Cs: Color): Color {.inline.} = - result.r = blendMultiply(Cb.r, Cs.r) - result.g = blendMultiply(Cb.g, Cs.g) - result.b = blendMultiply(Cb.b, Cs.b) +proc blendMultiplyFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendMultiplyFloat(Cb.r, Cs.r) + result.g = blendMultiplyFloat(Cb.g, Cs.g) + result.b = blendMultiplyFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendLinearBurn(Cb, Cs: Color): Color {.inline.} = - result.r = blendLinearBurn(Cb.r, Cs.r) - result.g = blendLinearBurn(Cb.g, Cs.g) - result.b = blendLinearBurn(Cb.b, Cs.b) +proc blendLinearBurnFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendLinearBurnFloat(Cb.r, Cs.r) + result.g = blendLinearBurnFloat(Cb.g, Cs.g) + result.b = blendLinearBurnFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendColorBurn(Cb, Cs: Color): Color {.inline.} = - result.r = blendColorBurn(Cb.r, Cs.r) - result.g = blendColorBurn(Cb.g, Cs.g) - result.b = blendColorBurn(Cb.b, Cs.b) +proc blendColorBurnFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendColorBurnFloat(Cb.r, Cs.r) + result.g = blendColorBurnFloat(Cb.g, Cs.g) + result.b = blendColorBurnFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendLighten(Cb, Cs: Color): Color {.inline.} = - result.r = blendLighten(Cb.r, Cs.r) - result.g = blendLighten(Cb.g, Cs.g) - result.b = blendLighten(Cb.b, Cs.b) +proc blendLightenFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendLightenFloat(Cb.r, Cs.r) + result.g = blendLightenFloat(Cb.g, Cs.g) + result.b = blendLightenFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendScreen(Cb, Cs: Color): Color {.inline.} = - result.r = blendScreen(Cb.r, Cs.r) - result.g = blendScreen(Cb.g, Cs.g) - result.b = blendScreen(Cb.b, Cs.b) +proc blendScreenFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendScreenFloat(Cb.r, Cs.r) + result.g = blendScreenFloat(Cb.g, Cs.g) + result.b = blendScreenFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendLinearDodge(Cb, Cs: Color): Color {.inline.} = - result.r = blendLinearDodge(Cb.r, Cs.r) - result.g = blendLinearDodge(Cb.g, Cs.g) - result.b = blendLinearDodge(Cb.b, Cs.b) +proc blendLinearDodgeFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendLinearDodgeFloat(Cb.r, Cs.r) + result.g = blendLinearDodgeFloat(Cb.g, Cs.g) + result.b = blendLinearDodgeFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendColorDodge(Cb, Cs: Color): Color {.inline.} = - result.r = blendColorDodge(Cb.r, Cs.r) - result.g = blendColorDodge(Cb.g, Cs.g) - result.b = blendColorDodge(Cb.b, Cs.b) +proc blendColorDodgeFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendColorDodgeFloat(Cb.r, Cs.r) + result.g = blendColorDodgeFloat(Cb.g, Cs.g) + result.b = blendColorDodgeFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendOverlay(Cb, Cs: Color): Color {.inline.} = - result.r = blendOverlay(Cb.r, Cs.r) - result.g = blendOverlay(Cb.g, Cs.g) - result.b = blendOverlay(Cb.b, Cs.b) +proc blendOverlayFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendOverlayFloat(Cb.r, Cs.r) + result.g = blendOverlayFloat(Cb.g, Cs.g) + result.b = blendOverlayFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendHardLight(Cb, Cs: Color): Color {.inline.} = - result.r = blendHardLight(Cb.r, Cs.r) - result.g = blendHardLight(Cb.g, Cs.g) - result.b = blendHardLight(Cb.b, Cs.b) +proc blendHardLightFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendHardLightFloat(Cb.r, Cs.r) + result.g = blendHardLightFloat(Cb.g, Cs.g) + result.b = blendHardLightFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendSoftLight(Cb, Cs: Color): Color {.inline.} = - result.r = blendSoftLight(Cb.r, Cs.r) - result.g = blendSoftLight(Cb.g, Cs.g) - result.b = blendSoftLight(Cb.b, Cs.b) +proc blendSoftLightFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendSoftLightFloat(Cb.r, Cs.r) + result.g = blendSoftLightFloat(Cb.g, Cs.g) + result.b = blendSoftLightFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendDifference(Cb, Cs: Color): Color {.inline.} = - result.r = blendDifference(Cb.r, Cs.r) - result.g = blendDifference(Cb.g, Cs.g) - result.b = blendDifference(Cb.b, Cs.b) +proc blendDifferenceFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendDifferenceFloat(Cb.r, Cs.r) + result.g = blendDifferenceFloat(Cb.g, Cs.g) + result.b = blendDifferenceFloat(Cb.b, Cs.b) result = alphaFix(Cb, Cs, result) -proc blendExclusion(Cb, Cs: Color): Color {.inline.} = - result.r = blendExclusion(Cb.r, Cs.r) - result.g = blendExclusion(Cb.g, Cs.g) - result.b = blendExclusion(Cb.b, Cs.b) +proc blendExclusionFloats(Cb, Cs: Color): Color {.inline.} = + result.r = blendExclusionFloat(Cb.r, Cs.r) + result.g = blendExclusionFloat(Cb.g, Cs.g) + result.b = blendExclusionFloat(Cb.b, Cs.b) 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)) alphaFix(Cb, Cs, mixed) -proc blendLuminosity(Cb, Cs: Color): Color {.inline.} = +proc blendLuminosityFloats(Cb, Cs: Color): Color {.inline.} = let mixed = SetLum(Cb, Lum(Cs)) 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)) 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)) alphaFix(Cb, Cs, mixed) -proc blendMask(target, blend: Color): Color {.inline.} = +proc blendMaskFloats(target, blend: Color): Color {.inline.} = result.r = target.r result.g = target.g result.b = target.b 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.g = target.g result.b = target.b 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.g = target.g result.b = target.b 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.g = target.g result.b = target.b result.a = abs(target.a - blend.a) -proc blendOverwrite(target, blend: Color): Color {.inline.} = +proc blendOverwriteFloats(target, blend: Color): Color {.inline.} = 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.} = let ab = Cb.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.a = a.uint8 -proc blendNormal*(a, b: ColorRGBA): ColorRGBA = - # blendNormal(a.color, b.color).rgba +proc blendNormal(a, b: ColorRGBA): ColorRGBA = result.r = b.r result.g = b.g result.b = b.b result = alphaFix(a, b, result) -proc blendDarken*(a, b: ColorRGBA): ColorRGBA = - blendDarken(a.color, b.color).rgba +proc blendDarken(a, b: ColorRGBA): ColorRGBA = + blendDarkenFloats(a.color, b.color).rgba -proc blendMultiply*(a, b: ColorRGBA): ColorRGBA = - blendMultiply(a.color, b.color).rgba +proc blendMultiply(a, b: ColorRGBA): ColorRGBA = + blendMultiplyFloats(a.color, b.color).rgba -proc blendLinearBurn*(a, b: ColorRGBA): ColorRGBA = - blendLinearBurn(a.color, b.color).rgba +proc blendLinearBurn(a, b: ColorRGBA): ColorRGBA = + blendLinearBurnFloats(a.color, b.color).rgba -proc blendColorBurn*(a, b: ColorRGBA): ColorRGBA = - blendColorBurn(a.color, b.color).rgba +proc blendColorBurn(a, b: ColorRGBA): ColorRGBA = + blendColorBurnFloats(a.color, b.color).rgba -proc blendLighten*(a, b: ColorRGBA): ColorRGBA = - blendLighten(a.color, b.color).rgba +proc blendLighten(a, b: ColorRGBA): ColorRGBA = + blendLightenFloats(a.color, b.color).rgba -proc blendScreen*(a, b: ColorRGBA): ColorRGBA = - blendScreen(a.color, b.color).rgba +proc blendScreen(a, b: ColorRGBA): ColorRGBA = + blendScreenFloats(a.color, b.color).rgba -proc blendLinearDodge*(a, b: ColorRGBA): ColorRGBA = - blendLinearDodge(a.color, b.color).rgba +proc blendLinearDodge(a, b: ColorRGBA): ColorRGBA = + blendLinearDodgeFloats(a.color, b.color).rgba -proc blendColorDodge*(a, b: ColorRGBA): ColorRGBA = - blendColorDodge(a.color, b.color).rgba +proc blendColorDodge(a, b: ColorRGBA): ColorRGBA = + blendColorDodgeFloats(a.color, b.color).rgba -proc blendOverlay*(a, b: ColorRGBA): ColorRGBA = - blendOverlay(a.color, b.color).rgba +proc blendOverlay(a, b: ColorRGBA): ColorRGBA = + blendOverlayFloats(a.color, b.color).rgba -proc blendHardLight*(a, b: ColorRGBA): ColorRGBA = - blendHardLight(a.color, b.color).rgba +proc blendHardLight(a, b: ColorRGBA): ColorRGBA = + blendHardLightFloats(a.color, b.color).rgba -proc blendSoftLight*(a, b: ColorRGBA): ColorRGBA = - blendSoftLight(a.color, b.color).rgba +proc blendSoftLight(a, b: ColorRGBA): ColorRGBA = + blendSoftLightFloats(a.color, b.color).rgba -proc blendDifference*(a, b: ColorRGBA): ColorRGBA = - blendDifference(a.color, b.color).rgba +proc blendDifference(a, b: ColorRGBA): ColorRGBA = + blendDifferenceFloats(a.color, b.color).rgba -proc blendExclusion*(a, b: ColorRGBA): ColorRGBA = - blendExclusion(a.color, b.color).rgba +proc blendExclusion(a, b: ColorRGBA): ColorRGBA = + blendExclusionFloats(a.color, b.color).rgba -proc blendColor*(a, b: ColorRGBA): ColorRGBA = - blendColor(a.color, b.color).rgba +proc blendColor(a, b: ColorRGBA): ColorRGBA = + blendColorFloats(a.color, b.color).rgba -proc blendLuminosity*(a, b: ColorRGBA): ColorRGBA = - blendLuminosity(a.color, b.color).rgba +proc blendLuminosity(a, b: ColorRGBA): ColorRGBA = + blendLuminosityFloats(a.color, b.color).rgba -proc blendHue*(a, b: ColorRGBA): ColorRGBA = - blendHue(a.color, b.color).rgba +proc blendHue(a, b: ColorRGBA): ColorRGBA = + blendHueFloats(a.color, b.color).rgba -proc blendSaturation*(a, b: ColorRGBA): ColorRGBA = - blendSaturation(a.color, b.color).rgba +proc blendSaturation(a, b: ColorRGBA): ColorRGBA = + blendSaturationFloats(a.color, b.color).rgba -proc blendMask*(a, b: ColorRGBA): ColorRGBA = +proc blendMask(a, b: ColorRGBA): ColorRGBA = result.r = a.r result.g = a.g result.b = a.b result.a = min(a.a, b.a) -proc blendSubtractMask*(a, b: ColorRGBA): ColorRGBA = - blendSubtractMask(a.color, b.color).rgba +proc blendSubtractMask(a, b: ColorRGBA): ColorRGBA = + blendSubtractMaskFloats(a.color, b.color).rgba -proc blendIntersectMask*(a, b: ColorRGBA): ColorRGBA = - blendIntersectMask(a.color, b.color).rgba +proc blendIntersectMask(a, b: ColorRGBA): ColorRGBA = + blendIntersectMaskFloats(a.color, b.color).rgba -proc blendExcludeMask*(a, b: ColorRGBA): ColorRGBA = - blendExcludeMask(a.color, b.color).rgba +proc blendExcludeMask(a, b: ColorRGBA): ColorRGBA = + blendExcludeMaskFloats(a.color, b.color).rgba -proc blendOverwrite*(a, b: ColorRGBA): ColorRGBA = - blendOverwrite(a.color, b.color).rgba +proc blendOverwrite(a, b: ColorRGBA): ColorRGBA = + blendOverwriteFloats(a.color, b.color).rgba -proc mix*(blendMode: BlendMode, dest, src: ColorRGBA): ColorRGBA {.inline.} = +proc mixer*(blendMode: BlendMode): Mixer = 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 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) + of bmNormal: blendNormal + of bmDarken: blendDarken + of bmMultiply: blendMultiply + of bmLinearBurn: blendLinearBurn + of bmColorBurn: blendColorBurn + of bmLighten: blendLighten + of bmScreen: blendScreen + of bmLinearDodge: blendLinearDodge + of bmColorDodge: blendColorDodge + of bmOverlay: blendOverlay + of bmSoftLight: blendSoftLight + of bmHardLight: blendHardLight + of bmDifference: blendDifference + of bmExclusion: blendExclusion + of bmHue: blendHue + of bmSaturation: blendSaturation + of bmColor: blendColor + of bmLuminosity: blendLuminosity + of bmMask: blendMask + of bmOverwrite: blendOverwrite + of bmSubtractMask: blendSubtractMask + of bmIntersectMask: blendIntersectMask + of bmExcludeMask: blendExcludeMask diff --git a/src/pixie/images.nim b/src/pixie/images.nim index aee7451..e8ae865 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -1,4 +1,4 @@ -import chroma, blends, vmath, common, system/memory +import chroma, blends, vmath, common, nimsimd/sse2 type Image* = ref object @@ -70,19 +70,31 @@ proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} = proc fill*(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 + # SIMD fill until we run out of room. + let m = mm_set1_epi32(cast[int32](rgba)) + var i: int + while i < image.data.len - 4: + 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) = ## 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.g = 255 - rgba.g rgba.b = 255 - rgba.b rgba.a = 255 - rgba.a + image.data[j] = rgba proc subImage*(image: Image, x, y, w, h: int): Image = ## Gets a sub image of the main image. @@ -394,13 +406,14 @@ proc drawCorrect*(a, b: Image, mat: Mat3, blendMode: BlendMode) = minFilterBy2 /= 2 matInv = matInv * scale(vec2(0.5, 0.5)) + let mixer = blendMode.mixer() for y in 0 ..< a.height: for x in 0 ..< a.width: - let srcPos = matInv * vec2(x.float32 + h, y.float32 + h) - var rgba = a.getRgbaUnsafe(x, y) - let rgba2 = b.getRgbaSmooth(srcPos.x - h, srcPos.y - h) - rgba = blendMode.mix(rgba, rgba2) - a.setRgbaUnsafe(x, y, rgba) + let + srcPos = matInv * vec2(x.float32 + h, y.float32 + h) + rgba = a.getRgbaUnsafe(x, y) + rgba2 = b.getRgbaSmooth(srcPos.x - h, srcPos.y - h) + a.setRgbaUnsafe(x, y, mixer(rgba, rgba2)) const h = 0.5.float32 @@ -408,9 +421,10 @@ proc drawUber( a, b: Image, p, dx, dy: Vec2, lines: array[0..3, Segment], - blendMode: static[BlendMode], - smooth: static[bool] + blendMode: BlendMode, + smooth: bool ) = + let mixer = blendMode.mixer() for y in 0 ..< a.height: var xMin = 0 @@ -435,25 +449,24 @@ proc drawUber( xMin = xMin.clamp(0, a.width) xMax = xMax.clamp(0, a.width) - when blendMode == bmIntersectMask: + if blendMode == bmIntersectMask: if xMin > 0: zeroMem(a.getAddr(0, y), 4 * xMin) for x in xMin ..< xMax: - let srcPos = p + dx * float32(x) + dy * float32(y) let + srcPos = p + dx * float32(x) + dy * float32(y) xFloat = srcPos.x - h yFloat = srcPos.y - h - var rgba = a.getRgbaUnsafe(x, y) - var rgba2 = - when smooth: - b.getRgbaSmooth(xFloat, yFloat) - else: - b.getRgbaUnsafe(xFloat.round.int, yFloat.round.int) - rgba = blendMode.mixStatic(rgba, rgba2) - a.setRgbaUnsafe(x, y, rgba) + rgba = a.getRgbaUnsafe(x, y) + rgba2 = + if smooth: + b.getRgbaSmooth(xFloat, yFloat) + else: + b.getRgbaUnsafe(xFloat.round.int, yFloat.round.int) + a.setRgbaUnsafe(x, y, mixer(rgba, rgba2)) - when blendMode == bmIntersectMask: + if blendMode == bmIntersectMask: if a.width - xMax > 0: 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 mat[2, 0].fractional == 0.0 and mat[2, 1].fractional == 0.0) - if not 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) + a.drawUber(b, p, dx, dy, lines, blendMode, smooth) proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} = a.draw(b, translate(pos), blendMode) diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim index 0e9b9d9..606b4f3 100644 --- a/tests/benchmark_images.nim +++ b/tests/benchmark_images.nim @@ -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 x in 0 ..< a.width: 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 x in 0 ..< a.width: var rgba = a.getRgbaUnsafe(x, y) @@ -15,7 +23,14 @@ proc invertOriginal(a: Image) = rgba.a = 255 - rgba.a 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) let op = (255 * opacity).uint32 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 result.setRgbaUnsafe(x, y, rgba) -proc sharpOpacityOriginal(a: Image): Image = +proc sharpOpacity1(a: Image): Image = result = newImage(a.width, a.height) for y in 0 ..< a.height: for x in 0 ..< a.width: @@ -36,8 +51,13 @@ proc sharpOpacityOriginal(a: Image): Image = var a = newImage(2560, 1440) -timeIt "fillOriginal": - a.fillOriginal(rgba(255, 255, 255, 255)) +timeIt "fill1": + 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) keep(a) @@ -46,24 +66,43 @@ timeIt "fill": doAssert a[0, 0] == rgba(255, 255, 255, 255) keep(a) -timeIt "invertOriginal": - a.invertOriginal() +timeIt "fill1_rgba": + 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) timeIt "invert": a.invert() keep(a) -timeIt "applyOpacityOriginal": - a = a.applyOpacityOriginal(0.5) +timeIt "applyOpacity1": + a = a.applyOpacity1(0.5) keep(a) timeIt "applyOpacity": a.applyOpacity(0.5) keep(a) -timeIt "sharpOpacityOriginal": - a = a.sharpOpacityOriginal() +timeIt "sharpOpacity1": + a = a.sharpOpacity1() keep(a) timeIt "sharpOpacity": diff --git a/tests/benchmark_images_draw.nim b/tests/benchmark_images_draw.nim index ddd733a..65200ca 100644 --- a/tests/benchmark_images_draw.nim +++ b/tests/benchmark_images_draw.nim @@ -4,7 +4,7 @@ block: var c: Image var a = newImage(1000, 1000) a.fill(rgba(255, 0, 0, 255)) - var b = newImage(1000, 1000) + var b = newImage(500, 500) b.fill(rgba(0, 255, 0, 255)) timeIt "drawCorrect bmNormal": @@ -14,7 +14,7 @@ block: block: var a = newImage(1000, 1000) a.fill(rgba(255, 0, 0, 255)) - var b = newImage(1000, 1000) + var b = newImage(500, 500) b.fill(rgba(0, 255, 0, 255)) timeIt "draw bmNormal": @@ -24,7 +24,7 @@ block: block: var a = newImage(1000, 1000) a.fill(rgba(255, 0, 0, 255)) - var b = newImage(1000, 1000) + var b = newImage(500, 500) b.fill(rgba(0, 255, 0, 255)) timeIt "draw Smooth bmNormal": diff --git a/tests/test_png.nim b/tests/test_png.nim index 7fa59c1..719bb8b 100644 --- a/tests/test_png.nim +++ b/tests/test_png.nim @@ -17,10 +17,10 @@ for channels in 1 .. 4: var data: seq[uint8] for x in 0 ..< 16: for y in 0 ..< 16: - var components = newSeq[uint8](channels) - for i in 0 ..< channels: - components[i] = (x * 16).uint8 - data.add(components) + var components = newSeq[uint8](channels) + for i in 0 ..< channels: + components[i] = (x * 16).uint8 + data.add(components) let encoded = encodePng(16, 16, channels, data[0].addr, data.len) for file in pngSuiteCorruptedFiles: