diff --git a/src/pixie/common.nim b/src/pixie/common.nim index ef15b0c..409ff57 100644 --- a/src/pixie/common.nim +++ b/src/pixie/common.nim @@ -9,20 +9,43 @@ proc fractional*(v: float32): float32 {.inline.} = result = abs(v) result = result - floor(result) +proc lerp*(a, b: ColorRGBA, t: float32): ColorRGBA {.inline.} = + let x = round(t * 255).uint32 + result.r = ((a.r.uint32 * (255 - x) + b.r.uint32 * x) div 255).uint8 + result.g = ((a.g.uint32 * (255 - x) + b.g.uint32 * x) div 255).uint8 + result.b = ((a.b.uint32 * (255 - x) + b.b.uint32 * x) div 255).uint8 + result.a = ((a.a.uint32 * (255 - x) + b.a.uint32 * x) div 255).uint8 + +proc premultiplyAlpha*(c: ColorRGBA): ColorRGBA {.inline.} = + ## Converts a color to premultiplied alpha from straight alpha. + result.r = ((c.r.uint16 * c.a.uint16) div 255).uint8 + result.g = ((c.g.uint16 * c.a.uint16) div 255).uint8 + result.b = ((c.b.uint16 * c.a.uint16) div 255).uint8 + result.a = c.a + +proc straightenAlpha*(c: ColorRGBA): ColorRGBA {.inline.} = + ## Converts a color to from premultiplied alpha to straight alpha. + result = c + if result.a != 0 and result.a != 255: + let multiplier = ((255 / c.a.float32) * 255).uint32 + result.r = ((result.r.uint32 * multiplier) div 255).uint8 + result.g = ((result.g.uint32 * multiplier) div 255).uint8 + result.b = ((result.b.uint32 * multiplier) div 255).uint8 + func lerp*(a, b: Color, v: float32): Color {.inline.} = result.r = lerp(a.r, b.r, v) result.g = lerp(a.g, b.g, v) result.b = lerp(a.b, b.b, v) result.a = lerp(a.a, b.a, v) -proc toAlphy*(c: Color): Color = +proc toAlphy*(c: Color): Color {.inline.} = ## Converts a color to premultiplied alpha from straight. result.r = c.r * c.a result.g = c.g * c.a result.b = c.b * c.a result.a = c.a -proc fromAlphy*(c: Color): Color = +proc fromAlphy*(c: Color): Color {.inline.} = ## Converts a color to from premultiplied alpha to straight. if c.a == 0: return diff --git a/src/pixie/images.nim b/src/pixie/images.nim index b11de60..7e89f5a 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -250,23 +250,22 @@ proc invert*(image: Image) = image.data[j] = rgba proc getRgbaSmooth*(image: Image, x, y: float32): ColorRGBA {.inline.} = - ## Gets a pixel as (x, y) floats. let minX = x.floor.int difX = x - x.floor minY = y.floor.int difY = y - y.floor - vX0Y0 = image[minX, minY].color().toAlphy() - vX1Y0 = image[minX + 1, minY].color().toAlphy() - vX0Y1 = image[minX, minY + 1].color().toAlphy() - vX1Y1 = image[minX + 1, minY + 1].color().toAlphy() + vX0Y0 = image[minX, minY].premultiplyAlpha() + vX1Y0 = image[minX + 1, minY].premultiplyAlpha() + vX0Y1 = image[minX, minY + 1].premultiplyAlpha() + vX1Y1 = image[minX + 1, minY + 1].premultiplyAlpha() bottomMix = lerp(vX0Y0, vX1Y0, difX) topMix = lerp(vX0Y1, vX1Y1, difX) finalMix = lerp(bottomMix, topMix, difY) - return finalMix.fromAlphy().rgba() + finalMix.straightenAlpha() proc resize*(srcImage: Image, width, height: int): Image = result = newImage(width, height) diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim index e7b10fb..32b28fe 100644 --- a/tests/benchmark_images.nim +++ b/tests/benchmark_images.nim @@ -5,12 +5,10 @@ let a = newImage(2560, 1440) timeIt "fill": a.fill(rgba(255, 255, 255, 255)) doAssert a[0, 0] == rgba(255, 255, 255, 255) - keep(a) timeIt "fill_rgba": a.fill(rgba(63, 127, 191, 191)) doAssert a[0, 0] == rgba(63, 127, 191, 191) - keep(a) timeIt "subImage": keep a.subImage(0, 0, 256, 256) @@ -34,3 +32,19 @@ timeIt "toAlphy": timeIt "fromAlphy": a.fromAlphy() + +timeIt "lerp integers": + for i in 0 ..< 100000: + let c = a[0, 0] + var z: int + for t in 0 .. 100: + z += lerp(c, c, t.float32 / 100).a.int + doAssert z > 0 + +timeIt "lerp floats": + for i in 0 ..< 100000: + let c = a[0, 0] + var z: int + for t in 0 .. 100: + z += lerp(c.color, c.color, t.float32 / 100).rgba().a.int + doAssert z > 0