Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 50 additions & 49 deletions src/pixie/images.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ type
width*, height*: int
data*: seq[ColorRGBX]

UnsafeImage = object
image: Image

when defined(release):
{.push checks: off.}

Expand Down Expand Up @@ -58,31 +61,32 @@ proc inside*(image: Image, x, y: int): bool {.inline, raises: [].} =
proc dataIndex*(image: Image, x, y: int): int {.inline, raises: [].} =
image.width * y + x

proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBX {.inline, raises: [].} =
template unsafe*(src: Image): UnsafeImage =
UnsafeImage(image: src)

template `[]`*(view: UnsafeImage, x, y: int): ColorRGBX =
## Gets a color from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will cause unsafe memory reads.
image.data[image.dataIndex(x, y)]
view.image.data[view.image.dataIndex(x, y)]

proc setRgbaUnsafe*(
image: Image, x, y: int, color: ColorRGBX
) {.inline, raises: [].} =
template `[]=`*(view: UnsafeImage, x, y: int, color: ColorRGBX) =
## Sets a color from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will cause unsafe memory writes.
image.data[image.dataIndex(x, y)] = color
view.image.data[view.image.dataIndex(x, y)] = color

proc `[]`*(image: Image, x, y: int): ColorRGBX {.inline, raises: [].} =
## Gets a pixel at (x, y) or returns transparent black if outside of bounds.
if image.inside(x, y):
return image.getRgbaUnsafe(x, y)
return image.unsafe[x, y]

proc `[]=`*(image: Image, x, y: int, color: SomeColor) {.inline, raises: [].} =
## Sets a pixel at (x, y) or does nothing if outside of bounds.
if image.inside(x, y):
image.setRgbaUnsafe(x, y, color.asRgbx())
image.unsafe[x, y] = color.asRgbx()

proc getColor*(image: Image, x, y: int): Color {.inline, raises: [].} =
## Gets a color at (x, y) or returns transparent black if outside of bounds.
Expand Down Expand Up @@ -239,7 +243,7 @@ proc diff*(master, image: Image): (float32, Image) {.raises: [PixieError].} =
c.g = diff.clamp(0, 255).uint8
c.b = (-diff).clamp(0, 255).uint8
c.a = 255
diffImage.setRgbaUnsafe(x, y, c)
diffImage.unsafe[x, y] = c
diffScore += abs(m.r.int - u.r.int) +
abs(m.g.int - u.g.int) +
abs(m.b.int - u.b.int) +
Expand Down Expand Up @@ -316,42 +320,39 @@ proc minifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =

for x in x ..< resultEvenWidth:
let
a = src.getRgbaUnsafe(x * 2 + 0, y * 2 + 0)
b = src.getRgbaUnsafe(x * 2 + 1, y * 2 + 0)
c = src.getRgbaUnsafe(x * 2 + 1, y * 2 + 1)
d = src.getRgbaUnsafe(x * 2 + 0, y * 2 + 1)
a = src.unsafe[x * 2 + 0, y * 2 + 0]
b = src.unsafe[x * 2 + 1, y * 2 + 0]
c = src.unsafe[x * 2 + 1, y * 2 + 1]
d = src.unsafe[x * 2 + 0, y * 2 + 1]
rgba = rgbx(
((a.r.uint32 + b.r + c.r + d.r) div 4).uint8,
((a.g.uint32 + b.g + c.g + d.g) div 4).uint8,
((a.b.uint32 + b.b + c.b + d.b) div 4).uint8,
((a.a.uint32 + b.a + c.a + d.a) div 4).uint8
)

result.setRgbaUnsafe(x, y, rgba)
result.unsafe[x, y] = rgba

if srcWidthIsOdd:
let rgbx = mix(
src.getRgbaUnsafe(src.width - 1, y * 2 + 0),
src.getRgbaUnsafe(src.width - 1, y * 2 + 1),
src.unsafe[src.width - 1, y * 2 + 0],
src.unsafe[src.width - 1, y * 2 + 1],
0.5
) * 0.5
result.setRgbaUnsafe(result.width - 1, y, rgbx)
result.unsafe[result.width - 1, y] = rgbx

if srcHeightIsOdd:
for x in 0 ..< resultEvenWidth:
let rgbx = mix(
src.getRgbaUnsafe(x * 2 + 0, src.height - 1),
src.getRgbaUnsafe(x * 2 + 1, src.height - 1),
src.unsafe[x * 2 + 0, src.height - 1],
src.unsafe[x * 2 + 1, src.height - 1],
0.5
) * 0.5
result.setRgbaUnsafe(x, result.height - 1, rgbx)
result.unsafe[x, result.height - 1] = rgbx

if srcWidthIsOdd:
result.setRgbaUnsafe(
result.width - 1,
result.height - 1,
src.getRgbaUnsafe(src.width - 1, src.height - 1) * 0.25
)
result.unsafe[result.width - 1, result.height - 1] =
src.unsafe[src.width - 1, src.height - 1] * 0.25

# Set src as this result for if we do another power
src = result
Expand Down Expand Up @@ -384,7 +385,7 @@ proc magnifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
x += 2
for _ in x ..< image.width:
let
rgbx = image.getRgbaUnsafe(x, y)
rgbx = image.unsafe[x, y]
resultIdx = result.dataIndex(x * scale, y * scale)
for i in 0 ..< scale:
result.data[resultIdx + i] = rgbx
Expand Down Expand Up @@ -547,10 +548,10 @@ proc blur*(
for xx in x - radius ..< min(x + radius, 0):
values += outOfBounds * kernel[xx - x + radius]
for xx in max(x - radius, 0) .. min(x + radius, image.width - 1):
values += image.getRgbaUnsafe(xx, y) * kernel[xx - x + radius]
values += image.unsafe[xx, y] * kernel[xx - x + radius]
for xx in max(x - radius, image.width) .. x + radius:
values += outOfBounds * kernel[xx - x + radius]
blurX.setRgbaUnsafe(y, x, rgbx(values))
blurX.unsafe[y, x] = rgbx(values)

# Blur in the Y direction.
for y in 0 ..< image.height:
Expand All @@ -559,10 +560,10 @@ proc blur*(
for yy in y - radius ..< min(y + radius, 0):
values += outOfBounds * kernel[yy - y + radius]
for yy in max(y - radius, 0) .. min(y + radius, image.height - 1):
values += blurX.getRgbaUnsafe(yy, x) * kernel[yy - y + radius]
values += blurX.unsafe[yy, x] * kernel[yy - y + radius]
for yy in max(y - radius, image.height) .. y + radius:
values += outOfBounds * kernel[yy - y + radius]
image.setRgbaUnsafe(x, y, rgbx(values))
image.unsafe[x, y] = rgbx(values)

proc newMask*(image: Image): Mask {.raises: [PixieError].} =
## Returns a new mask using the alpha values of the image.
Expand Down Expand Up @@ -611,10 +612,10 @@ proc getRgbaSmooth*(

var x0y0, x1y0, x0y1, x1y1: ColorRGBX
if wrapped:
x0y0 = image.getRgbaUnsafe(x0 mod image.width, y0 mod image.height)
x1y0 = image.getRgbaUnsafe(x1 mod image.width, y0 mod image.height)
x0y1 = image.getRgbaUnsafe(x0 mod image.width, y1 mod image.height)
x1y1 = image.getRgbaUnsafe(x1 mod image.width, y1 mod image.height)
x0y0 = image.unsafe[x0 mod image.width, y0 mod image.height]
x1y0 = image.unsafe[x1 mod image.width, y0 mod image.height]
x0y1 = image.unsafe[x0 mod image.width, y1 mod image.height]
x1y1 = image.unsafe[x1 mod image.width, y1 mod image.height]
else:
x0y0 = image[x0, y0]
x1y0 = image[x1, y0]
Expand Down Expand Up @@ -677,7 +678,7 @@ proc drawCorrect(
yFloat = samplePos.y - h

when type(a) is Image:
let backdrop = a.getRgbaUnsafe(x, y)
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let
sample = b.getRgbaSmooth(xFloat, yFloat, tiled)
Expand All @@ -686,9 +687,9 @@ proc drawCorrect(
let
sample = b.getValueSmooth(xFloat, yFloat)
blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.setRgbaUnsafe(x, y, blended)
a.unsafe[x, y] = blended
else: # a is a Mask
let backdrop = a.getValueUnsafe(x, y)
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let sample = b.getRgbaSmooth(xFloat, yFloat, tiled).a
else: # b is a Mask
Expand Down Expand Up @@ -791,7 +792,7 @@ proc drawUber(

for x in xMin ..< xMax:
when type(a) is Image:
let backdrop = a.getRgbaUnsafe(x, y)
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let
sample = b.getRgbaSmooth(srcPos.x, srcPos.y)
Expand All @@ -800,14 +801,14 @@ proc drawUber(
let
sample = b.getValueSmooth(srcPos.x, srcPos.y)
blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.setRgbaUnsafe(x, y, blended)
a.unsafe[x, y] = blended
else: # a is a Mask
let backdrop = a.getValueUnsafe(x, y)
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let sample = b.getRgbaSmooth(srcPos.x, srcPos.y).a
else: # b is a Mask
let sample = b.getValueSmooth(srcPos.x, srcPos.y)
a.setValueUnsafe(x, y, masker(backdrop, sample))
a.unsafe[x, y] = masker(backdrop, sample)

srcPos += dx

Expand Down Expand Up @@ -892,23 +893,23 @@ proc drawUber(
let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)

when type(a) is Image:
let backdrop = a.getRgbaUnsafe(x, y)
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let
sample = b.getRgbaUnsafe(samplePos.x, samplePos.y)
sample = b.unsafe[samplePos.x, samplePos.y]
blended = blender(backdrop, sample)
else: # b is a Mask
let
sample = b.getValueUnsafe(samplePos.x, samplePos.y)
sample = b.unsafe[samplePos.x, samplePos.y]
blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.setRgbaUnsafe(x, y, blended)
a.unsafe[x, y] = blended
else: # a is a Mask
let backdrop = a.getValueUnsafe(x, y)
let backdrop = a.unsafe[x, y]
when type(b) is Image:
let sample = b.getRgbaUnsafe(samplePos.x, samplePos.y).a
let sample = b.unsafe[samplePos.x, samplePos.y].a
else: # b is a Mask
let sample = b.getValueUnsafe(samplePos.x, samplePos.y)
a.setValueUnsafe(x, y, masker(backdrop, sample))
let sample = b.unsafe[samplePos.x, samplePos.y]
a.unsafe[x, y] = masker(backdrop, sample)

srcPos += dx

Expand Down
46 changes: 26 additions & 20 deletions src/pixie/masks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type
width*, height*: int
data*: seq[uint8]

UnsafeMask = object
mask: Mask

when defined(release):
{.push checks: off.}

Expand Down Expand Up @@ -38,29 +41,32 @@ proc inside*(mask: Mask, x, y: int): bool {.inline, raises: [].} =
proc dataIndex*(mask: Mask, x, y: int): int {.inline, raises: [].} =
mask.width * y + x

proc getValueUnsafe*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
template unsafe*(src: Mask): UnsafeMask =
UnsafeMask(mask: src)

template `[]`*(view: UnsafeMask, x, y: int): uint8 =
## Gets a value from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory reads.
result = mask.data[mask.dataIndex(x, y)]
view.mask.data[view.mask.dataIndex(x, y)]

proc setValueUnsafe*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} =
template `[]=`*(view: UnsafeMask, x, y: int, color: uint8) =
## Sets a value from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory writes.
mask.data[mask.dataIndex(x, y)] = value
view.mask.data[view.mask.dataIndex(x, y)] = color

proc `[]`*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
## Gets a value at (x, y) or returns transparent black if outside of bounds.
if mask.inside(x, y):
return mask.getValueUnsafe(x, y)
return mask.unsafe[x, y]

proc `[]=`*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} =
## Sets a value at (x, y) or does nothing if outside of bounds.
if mask.inside(x, y):
mask.setValueUnsafe(x, y, value)
mask.unsafe[x, y] = value

proc getValue*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
## Gets a value at (x, y) or returns transparent black if outside of bounds.
Expand Down Expand Up @@ -144,11 +150,11 @@ proc minifyBy2*(mask: Mask, power = 1): Mask {.raises: [PixieError].} =

for x in x ..< result.width:
let value =
src.getValueUnsafe(x * 2 + 0, y * 2 + 0).uint32 +
src.getValueUnsafe(x * 2 + 1, y * 2 + 0) +
src.getValueUnsafe(x * 2 + 1, y * 2 + 1) +
src.getValueUnsafe(x * 2 + 0, y * 2 + 1)
result.setValueUnsafe(x, y, (value div 4).uint8)
src.unsafe[x * 2 + 0, y * 2 + 0].uint32 +
src.unsafe[x * 2 + 1, y * 2 + 0] +
src.unsafe[x * 2 + 1, y * 2 + 1] +
src.unsafe[x * 2 + 0, y * 2 + 1]
result.unsafe[x, y] = (value div 4).uint8

# Set src as this result for if we do another power
src = result
Expand All @@ -163,7 +169,7 @@ proc magnifyBy2*(mask: Mask, power = 1): Mask {.raises: [PixieError].} =
for y in 0 ..< result.height:
for x in 0 ..< mask.width:
let
value = mask.getValueUnsafe(x, y div scale)
value = mask.unsafe[x, y div scale]
scaledX = x * scale
idx = result.dataIndex(scaledX, y)
for i in 0 ..< scale:
Expand Down Expand Up @@ -222,24 +228,24 @@ proc spread*(mask: Mask, spread: float32) {.raises: [PixieError].} =
for x in 0 ..< mask.width:
var maxValue: uint8
for xx in max(x - spread, 0) .. min(x + spread, mask.width - 1):
let value = mask.getValueUnsafe(xx, y)
let value = mask.unsafe[xx, y]
if value > maxValue:
maxValue = value
if maxValue == 255:
break
spreadX.setValueUnsafe(y, x, maxValue)
spreadX.unsafe[y, x] = maxValue

# Spread in the Y direction and modify mask.
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
var maxValue: uint8
for yy in max(y - spread, 0) .. min(y + spread, mask.height - 1):
let value = spreadX.getValueUnsafe(yy, x)
let value = spreadX.unsafe[yy, x]
if value > maxValue:
maxValue = value
if maxValue == 255:
break
mask.setValueUnsafe(x, y, maxValue)
mask.unsafe[x, y] = maxValue

proc ceil*(mask: Mask) {.raises: [].} =
## A value of 0 stays 0. Anything else turns into 255.
Expand Down Expand Up @@ -277,10 +283,10 @@ proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) {.raises: [Pixie
for xx in x - radius ..< min(x + radius, 0):
value += outOfBounds * kernel[xx - x + radius].uint32
for xx in max(x - radius, 0) .. min(x + radius, mask.width - 1):
value += mask.getValueUnsafe(xx, y) * kernel[xx - x + radius].uint32
value += mask.unsafe[xx, y] * kernel[xx - x + radius].uint32
for xx in max(x - radius, mask.width) .. x + radius:
value += outOfBounds * kernel[xx - x + radius].uint32
blurX.setValueUnsafe(y, x, (value div 256 div 255).uint8)
blurX.unsafe[y, x] = (value div 256 div 255).uint8

# Blur in the Y direction and modify image.
for y in 0 ..< mask.height:
Expand All @@ -289,10 +295,10 @@ proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) {.raises: [Pixie
for yy in y - radius ..< min(y + radius, 0):
value += outOfBounds * kernel[yy - y + radius].uint32
for yy in max(y - radius, 0) .. min(y + radius, mask.height - 1):
value += blurX.getValueUnsafe(yy, x) * kernel[yy - y + radius].uint32
value += blurX.unsafe[yy, x] * kernel[yy - y + radius].uint32
for yy in max(y - radius, mask.height) .. y + radius:
value += outOfBounds * kernel[yy - y + radius].uint32
mask.setValueUnsafe(x, y, (value div 256 div 255).uint8)
mask.unsafe[x, y] = (value div 256 div 255).uint8

when defined(release):
{.pop.}
Loading