Merge pull request #324 from guzba/master
faster vertical and horizontal gradients
This commit is contained in:
commit
463c8249b2
6 changed files with 157 additions and 32 deletions
|
@ -976,12 +976,18 @@ proc shadow*(
|
||||||
image: Image, offset: Vec2, spread, blur: float32, color: SomeColor
|
image: Image, offset: Vec2, spread, blur: float32, color: SomeColor
|
||||||
): Image {.raises: [PixieError].} =
|
): Image {.raises: [PixieError].} =
|
||||||
## Create a shadow of the image with the offset, spread and blur.
|
## Create a shadow of the image with the offset, spread and blur.
|
||||||
let
|
let mask = image.newMask()
|
||||||
mask = image.newMask()
|
|
||||||
|
var shifted: Mask
|
||||||
|
if offset == vec2(0, 0):
|
||||||
|
shifted = mask
|
||||||
|
else:
|
||||||
shifted = newMask(mask.width, mask.height)
|
shifted = newMask(mask.width, mask.height)
|
||||||
shifted.draw(mask, translate(offset), bmOverwrite)
|
shifted.draw(mask, translate(offset), bmOverwrite)
|
||||||
|
|
||||||
shifted.spread(spread)
|
shifted.spread(spread)
|
||||||
shifted.blur(blur)
|
shifted.blur(blur)
|
||||||
|
|
||||||
result = newImage(shifted.width, shifted.height)
|
result = newImage(shifted.width, shifted.height)
|
||||||
result.fill(color)
|
result.fill(color)
|
||||||
result.draw(shifted, blendMode = bmMask)
|
result.draw(shifted, blendMode = bmMask)
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import blends, chroma, common, images, vmath
|
import blends, chroma, common, images, vmath
|
||||||
|
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
import nimsimd/sse2
|
||||||
|
|
||||||
type
|
type
|
||||||
PaintKind* = enum
|
PaintKind* = enum
|
||||||
pkSolid
|
pkSolid
|
||||||
|
@ -45,9 +48,7 @@ proc newPaint*(paint: Paint): Paint {.raises: [].} =
|
||||||
result.gradientHandlePositions = paint.gradientHandlePositions
|
result.gradientHandlePositions = paint.gradientHandlePositions
|
||||||
result.gradientStops = paint.gradientStops
|
result.gradientStops = paint.gradientStops
|
||||||
|
|
||||||
converter parseSomePaint*(
|
converter parseSomePaint*(paint: SomePaint): Paint {.inline.} =
|
||||||
paint: SomePaint
|
|
||||||
): Paint {.inline.} =
|
|
||||||
## Given SomePaint, parse it in different ways.
|
## Given SomePaint, parse it in different ways.
|
||||||
when type(paint) is string:
|
when type(paint) is string:
|
||||||
result = newPaint(pkSolid)
|
result = newPaint(pkSolid)
|
||||||
|
@ -67,19 +68,10 @@ converter parseSomePaint*(
|
||||||
proc colorStop*(color: Color, position: float32): ColorStop =
|
proc colorStop*(color: Color, position: float32): ColorStop =
|
||||||
ColorStop(color: color, position: position)
|
ColorStop(color: color, position: position)
|
||||||
|
|
||||||
proc toLineSpace(at, to, point: Vec2): float32 {.inline.} =
|
proc gradientColor(paint: Paint, t: float32): ColorRGBX =
|
||||||
## Convert position on to where it would fall on a line between at and to.
|
## Get the gradient color based on `t` - where are we related to a line.
|
||||||
let
|
|
||||||
d = to - at
|
|
||||||
det = d.x * d.x + d.y * d.y
|
|
||||||
(d.y * (point.y - at.y) + d.x * (point.x - at.x)) / det
|
|
||||||
|
|
||||||
proc gradientPut(
|
|
||||||
image: Image, paint: Paint, x, y: int, t: float32, stops: seq[ColorStop]
|
|
||||||
) =
|
|
||||||
## Put an gradient color based on `t` - where are we related to a line.
|
|
||||||
var index = -1
|
var index = -1
|
||||||
for i, stop in stops:
|
for i, stop in paint.gradientStops:
|
||||||
if stop.position < t:
|
if stop.position < t:
|
||||||
index = i
|
index = i
|
||||||
if stop.position > t:
|
if stop.position > t:
|
||||||
|
@ -87,21 +79,21 @@ proc gradientPut(
|
||||||
var color: Color
|
var color: Color
|
||||||
if index == -1:
|
if index == -1:
|
||||||
# first stop solid
|
# first stop solid
|
||||||
color = stops[0].color
|
color = paint.gradientStops[0].color
|
||||||
elif index + 1 >= stops.len:
|
elif index + 1 >= paint.gradientStops.len:
|
||||||
# last stop solid
|
# last stop solid
|
||||||
color = stops[index].color
|
color = paint.gradientStops[index].color
|
||||||
else:
|
else:
|
||||||
let
|
let
|
||||||
gs1 = stops[index]
|
gs1 = paint.gradientStops[index]
|
||||||
gs2 = stops[index + 1]
|
gs2 = paint.gradientStops[index + 1]
|
||||||
color = mix(
|
color = mix(
|
||||||
gs1.color,
|
gs1.color,
|
||||||
gs2.color,
|
gs2.color,
|
||||||
(t - gs1.position) / (gs2.position - gs1.position)
|
(t - gs1.position) / (gs2.position - gs1.position)
|
||||||
)
|
)
|
||||||
color.a *= paint.opacity
|
color.a *= paint.opacity
|
||||||
image.setRgbaUnsafe(x, y, color.rgbx())
|
color.rgbx()
|
||||||
|
|
||||||
proc fillGradientLinear(image: Image, paint: Paint) =
|
proc fillGradientLinear(image: Image, paint: Paint) =
|
||||||
## Fills a linear gradient.
|
## Fills a linear gradient.
|
||||||
|
@ -115,15 +107,66 @@ proc fillGradientLinear(image: Image, paint: Paint) =
|
||||||
if paint.opacity == 0:
|
if paint.opacity == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
proc toLineSpace(at, to, point: Vec2): float32 {.inline.} =
|
||||||
|
## Convert position on to where it would fall on a line between at and to.
|
||||||
|
let
|
||||||
|
d = to - at
|
||||||
|
det = d.x * d.x + d.y * d.y
|
||||||
|
(d.y * (point.y - at.y) + d.x * (point.x - at.x)) / det
|
||||||
|
|
||||||
let
|
let
|
||||||
at = paint.gradientHandlePositions[0]
|
at = paint.gradientHandlePositions[0]
|
||||||
to = paint.gradientHandlePositions[1]
|
to = paint.gradientHandlePositions[1]
|
||||||
|
|
||||||
|
if at.y == to.y: # Horizontal gradient
|
||||||
|
var x: int
|
||||||
|
while x < image.width:
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
if x + 4 <= image.width:
|
||||||
|
var colors: array[4, ColorRGBX]
|
||||||
|
for i in 0 ..< 4:
|
||||||
|
let
|
||||||
|
xy = vec2((x + i).float32, 0.float32)
|
||||||
|
t = toLineSpace(at, to, xy)
|
||||||
|
rgbx = paint.gradientColor(t)
|
||||||
|
colors[i] = rgbx
|
||||||
|
|
||||||
|
let colorVec = cast[M128i](colors)
|
||||||
|
for y in 0 ..< image.height:
|
||||||
|
mm_storeu_si128(image.data[image.dataIndex(x, y)].addr, colorVec)
|
||||||
|
x += 4
|
||||||
|
continue
|
||||||
|
|
||||||
|
let
|
||||||
|
xy = vec2(x.float32, 0.float32)
|
||||||
|
t = toLineSpace(at, to, xy)
|
||||||
|
rgbx = paint.gradientColor(t)
|
||||||
|
for y in 0 ..< image.height:
|
||||||
|
image.setRgbaUnsafe(x, y, rgbx)
|
||||||
|
inc x
|
||||||
|
|
||||||
|
elif at.x == to.x: # Vertical gradient
|
||||||
|
for y in 0 ..< image.height:
|
||||||
|
let
|
||||||
|
xy = vec2(0.float32, y.float32)
|
||||||
|
t = toLineSpace(at, to, xy)
|
||||||
|
rgbx = paint.gradientColor(t)
|
||||||
|
var x: int
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
let colorVec = mm_set1_epi32(cast[int32](rgbx))
|
||||||
|
for _ in 0 ..< image.width div 4:
|
||||||
|
mm_storeu_si128(image.data[image.dataIndex(x, y)].addr, colorVec)
|
||||||
|
x += 4
|
||||||
|
for x in x ..< image.width:
|
||||||
|
image.setRgbaUnsafe(x, y, rgbx)
|
||||||
|
|
||||||
|
else:
|
||||||
for y in 0 ..< image.height:
|
for y in 0 ..< image.height:
|
||||||
for x in 0 ..< image.width:
|
for x in 0 ..< image.width:
|
||||||
let
|
let
|
||||||
xy = vec2(x.float32, y.float32)
|
xy = vec2(x.float32, y.float32)
|
||||||
t = toLineSpace(at, to, xy)
|
t = toLineSpace(at, to, xy)
|
||||||
image.gradientPut(paint, x, y, t, paint.gradientStops)
|
image.setRgbaUnsafe(x, y, paint.gradientColor(t))
|
||||||
|
|
||||||
proc fillGradientRadial(image: Image, paint: Paint) =
|
proc fillGradientRadial(image: Image, paint: Paint) =
|
||||||
## Fills a radial gradient.
|
## Fills a radial gradient.
|
||||||
|
@ -154,7 +197,7 @@ proc fillGradientRadial(image: Image, paint: Paint) =
|
||||||
let
|
let
|
||||||
xy = vec2(x.float32, y.float32)
|
xy = vec2(x.float32, y.float32)
|
||||||
t = (mat * xy).length()
|
t = (mat * xy).length()
|
||||||
image.gradientPut(paint, x, y, t, paint.gradientStops)
|
image.setRgbaUnsafe(x, y, paint.gradientColor(t))
|
||||||
|
|
||||||
proc fillGradientAngular(image: Image, paint: Paint) =
|
proc fillGradientAngular(image: Image, paint: Paint) =
|
||||||
## Fills an angular gradient.
|
## Fills an angular gradient.
|
||||||
|
@ -171,6 +214,7 @@ proc fillGradientAngular(image: Image, paint: Paint) =
|
||||||
let
|
let
|
||||||
center = paint.gradientHandlePositions[0]
|
center = paint.gradientHandlePositions[0]
|
||||||
edge = paint.gradientHandlePositions[1]
|
edge = paint.gradientHandlePositions[1]
|
||||||
|
f32PI = PI.float32
|
||||||
# TODO: make edge between start and end anti-aliased.
|
# TODO: make edge between start and end anti-aliased.
|
||||||
let gradientAngle = normalize(edge - center).angle().fixAngle()
|
let gradientAngle = normalize(edge - center).angle().fixAngle()
|
||||||
for y in 0 ..< image.height:
|
for y in 0 ..< image.height:
|
||||||
|
@ -178,8 +222,8 @@ proc fillGradientAngular(image: Image, paint: Paint) =
|
||||||
let
|
let
|
||||||
xy = vec2(x.float32, y.float32)
|
xy = vec2(x.float32, y.float32)
|
||||||
angle = normalize(xy - center).angle()
|
angle = normalize(xy - center).angle()
|
||||||
t = (angle + gradientAngle + PI / 2).fixAngle() / 2 / PI + 0.5
|
t = (angle + gradientAngle + f32PI / 2).fixAngle() / 2 / f32PI + 0.5.float32
|
||||||
image.gradientPut(paint, x, y, t, paint.gradientStops)
|
image.setRgbaUnsafe(x, y, paint.gradientColor(t))
|
||||||
|
|
||||||
proc fillGradient*(image: Image, paint: Paint) {.raises: [PixieError].} =
|
proc fillGradient*(image: Image, paint: Paint) {.raises: [PixieError].} =
|
||||||
## Fills with the Paint gradient.
|
## Fills with the Paint gradient.
|
||||||
|
|
|
@ -115,7 +115,7 @@ block:
|
||||||
a = newImage(100, 100)
|
a = newImage(100, 100)
|
||||||
b = newImage(50, 50)
|
b = newImage(50, 50)
|
||||||
|
|
||||||
timeIt "shadow":
|
timeIt "shadow (no offset)":
|
||||||
b.fill(rgba(0, 0, 0, 255))
|
b.fill(rgba(0, 0, 0, 255))
|
||||||
a.draw(b, translate(vec2(25, 25)))
|
a.draw(b, translate(vec2(25, 25)))
|
||||||
|
|
||||||
|
@ -126,3 +126,20 @@ block:
|
||||||
color = rgba(0, 0, 0, 255)
|
color = rgba(0, 0, 0, 255)
|
||||||
)
|
)
|
||||||
keep(shadow)
|
keep(shadow)
|
||||||
|
|
||||||
|
block:
|
||||||
|
let
|
||||||
|
a = newImage(100, 100)
|
||||||
|
b = newImage(50, 50)
|
||||||
|
|
||||||
|
timeIt "shadow (with offset)":
|
||||||
|
b.fill(rgba(0, 0, 0, 255))
|
||||||
|
a.draw(b, translate(vec2(25, 25)))
|
||||||
|
|
||||||
|
let shadow = a.shadow(
|
||||||
|
offset = vec2(10, 10),
|
||||||
|
spread = 10,
|
||||||
|
blur = 10,
|
||||||
|
color = rgba(0, 0, 0, 255)
|
||||||
|
)
|
||||||
|
keep(shadow)
|
||||||
|
|
43
tests/benchmark_paints.nim
Normal file
43
tests/benchmark_paints.nim
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import benchy, pixie
|
||||||
|
|
||||||
|
let image = newImage(1000, 1000)
|
||||||
|
|
||||||
|
timeIt "GradientLinear vertical":
|
||||||
|
let paint = newPaint(pkGradientLinear)
|
||||||
|
paint.gradientHandlePositions = @[
|
||||||
|
vec2(50, 0),
|
||||||
|
vec2(50, 1000),
|
||||||
|
]
|
||||||
|
paint.gradientStops = @[
|
||||||
|
ColorStop(color: color(1, 0, 0, 1), position: 0),
|
||||||
|
ColorStop(color: color(1, 0, 0, 0.15625), position: 1.0),
|
||||||
|
]
|
||||||
|
image.fillGradient(paint)
|
||||||
|
|
||||||
|
timeIt "GradientLinear horizontal":
|
||||||
|
let paint = newPaint(pkGradientLinear)
|
||||||
|
paint.gradientHandlePositions = @[
|
||||||
|
vec2(0, 50),
|
||||||
|
vec2(1000, 50),
|
||||||
|
]
|
||||||
|
paint.gradientStops = @[
|
||||||
|
ColorStop(color: color(1, 0, 0, 1), position: 0),
|
||||||
|
ColorStop(color: color(1, 0, 0, 0.15625), position: 1.0),
|
||||||
|
]
|
||||||
|
image.fillGradient(paint)
|
||||||
|
|
||||||
|
# timeIt "GradientLinear radial":
|
||||||
|
# discard
|
||||||
|
|
||||||
|
timeIt "GradientLinear angular":
|
||||||
|
let paint = newPaint(pkGradientAngular)
|
||||||
|
paint.gradientHandlePositions = @[
|
||||||
|
vec2(500, 500),
|
||||||
|
vec2(1000, 500),
|
||||||
|
vec2(500, 1000)
|
||||||
|
]
|
||||||
|
paint.gradientStops = @[
|
||||||
|
ColorStop(color: color(1, 0, 0, 1), position: 0),
|
||||||
|
ColorStop(color: color(1, 0, 0, 0.15625), position: 1.0),
|
||||||
|
]
|
||||||
|
image.fillGradient(paint)
|
BIN
tests/paths/gradientLinear2.png
Normal file
BIN
tests/paths/gradientLinear2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -69,6 +69,21 @@ block:
|
||||||
image.fillPath(heartShape, paint)
|
image.fillPath(heartShape, paint)
|
||||||
image.writeFile("tests/paths/gradientLinear.png")
|
image.writeFile("tests/paths/gradientLinear.png")
|
||||||
|
|
||||||
|
block:
|
||||||
|
let paint = newPaint(pkGradientLinear)
|
||||||
|
paint.gradientHandlePositions = @[
|
||||||
|
vec2(50, 0),
|
||||||
|
vec2(50, 100),
|
||||||
|
]
|
||||||
|
paint.gradientStops = @[
|
||||||
|
ColorStop(color: color(1, 0, 0, 1), position: 0),
|
||||||
|
ColorStop(color: color(1, 0, 0, 0.15625), position: 1.0),
|
||||||
|
]
|
||||||
|
|
||||||
|
let image = newImage(100, 100)
|
||||||
|
image.fillPath(heartShape, paint)
|
||||||
|
image.writeFile("tests/paths/gradientLinear2.png")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let paint = newPaint(pkGradientRadial)
|
let paint = newPaint(pkGradientRadial)
|
||||||
paint.gradientHandlePositions = @[
|
paint.gradientHandlePositions = @[
|
||||||
|
|
Loading…
Reference in a new issue