image.unsafe[x, y]

This commit is contained in:
Ryan Oldenburg 2021-12-11 17:57:02 -06:00
parent 0b1fe8378e
commit ff1e6bd12d
3 changed files with 54 additions and 53 deletions

View file

@ -11,6 +11,9 @@ type
width*, height*: int width*, height*: int
data*: seq[ColorRGBX] data*: seq[ColorRGBX]
UnsafeImage = object
image: Image
when defined(release): when defined(release):
{.push checks: off.} {.push checks: off.}
@ -58,31 +61,32 @@ proc inside*(image: Image, x, y: int): bool {.inline, raises: [].} =
proc dataIndex*(image: Image, x, y: int): int {.inline, raises: [].} = proc dataIndex*(image: Image, x, y: int): int {.inline, raises: [].} =
image.width * y + x 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. ## Gets a color from (x, y) coordinates.
## * No bounds checking * ## * No bounds checking *
## Make sure that x, y are in bounds. ## Make sure that x, y are in bounds.
## Failure in the assumptions will cause unsafe memory reads. ## 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*( template `[]=`*(view: UnsafeImage, x, y: int, color: ColorRGBX) =
image: Image, x, y: int, color: ColorRGBX
) {.inline, raises: [].} =
## Sets a color from (x, y) coordinates. ## Sets a color from (x, y) coordinates.
## * No bounds checking * ## * No bounds checking *
## Make sure that x, y are in bounds. ## Make sure that x, y are in bounds.
## Failure in the assumptions will cause unsafe memory writes. ## 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: [].} = proc `[]`*(image: Image, x, y: int): ColorRGBX {.inline, raises: [].} =
## Gets a pixel at (x, y) or returns transparent black if outside of bounds. ## Gets a pixel at (x, y) or returns transparent black if outside of bounds.
if image.inside(x, y): 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: [].} = proc `[]=`*(image: Image, x, y: int, color: SomeColor) {.inline, raises: [].} =
## Sets a pixel at (x, y) or does nothing if outside of bounds. ## Sets a pixel at (x, y) or does nothing if outside of bounds.
if image.inside(x, y): 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: [].} = proc getColor*(image: Image, x, y: int): Color {.inline, raises: [].} =
## Gets a color at (x, y) or returns transparent black if outside of bounds. ## Gets a color at (x, y) or returns transparent black if outside of bounds.
@ -239,7 +243,7 @@ proc diff*(master, image: Image): (float32, Image) {.raises: [PixieError].} =
c.g = diff.clamp(0, 255).uint8 c.g = diff.clamp(0, 255).uint8
c.b = (-diff).clamp(0, 255).uint8 c.b = (-diff).clamp(0, 255).uint8
c.a = 255 c.a = 255
diffImage.setRgbaUnsafe(x, y, c) diffImage.unsafe[x, y] = c
diffScore += abs(m.r.int - u.r.int) + diffScore += abs(m.r.int - u.r.int) +
abs(m.g.int - u.g.int) + abs(m.g.int - u.g.int) +
abs(m.b.int - u.b.int) + abs(m.b.int - u.b.int) +
@ -316,10 +320,10 @@ proc minifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
for x in x ..< resultEvenWidth: for x in x ..< resultEvenWidth:
let let
a = src.getRgbaUnsafe(x * 2 + 0, y * 2 + 0) a = src.unsafe[x * 2 + 0, y * 2 + 0]
b = src.getRgbaUnsafe(x * 2 + 1, y * 2 + 0) b = src.unsafe[x * 2 + 1, y * 2 + 0]
c = src.getRgbaUnsafe(x * 2 + 1, y * 2 + 1) c = src.unsafe[x * 2 + 1, y * 2 + 1]
d = src.getRgbaUnsafe(x * 2 + 0, y * 2 + 1) d = src.unsafe[x * 2 + 0, y * 2 + 1]
rgba = rgbx( rgba = rgbx(
((a.r.uint32 + b.r + c.r + d.r) div 4).uint8, ((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.g.uint32 + b.g + c.g + d.g) div 4).uint8,
@ -327,31 +331,28 @@ proc minifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
((a.a.uint32 + b.a + c.a + d.a) 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: if srcWidthIsOdd:
let rgbx = mix( let rgbx = mix(
src.getRgbaUnsafe(src.width - 1, y * 2 + 0), src.unsafe[src.width - 1, y * 2 + 0],
src.getRgbaUnsafe(src.width - 1, y * 2 + 1), src.unsafe[src.width - 1, y * 2 + 1],
0.5 0.5
) * 0.5 ) * 0.5
result.setRgbaUnsafe(result.width - 1, y, rgbx) result.unsafe[result.width - 1, y] = rgbx
if srcHeightIsOdd: if srcHeightIsOdd:
for x in 0 ..< resultEvenWidth: for x in 0 ..< resultEvenWidth:
let rgbx = mix( let rgbx = mix(
src.getRgbaUnsafe(x * 2 + 0, src.height - 1), src.unsafe[x * 2 + 0, src.height - 1],
src.getRgbaUnsafe(x * 2 + 1, src.height - 1), src.unsafe[x * 2 + 1, src.height - 1],
0.5 0.5
) * 0.5 ) * 0.5
result.setRgbaUnsafe(x, result.height - 1, rgbx) result.unsafe[x, result.height - 1] = rgbx
if srcWidthIsOdd: if srcWidthIsOdd:
result.setRgbaUnsafe( result.unsafe[result.width - 1, result.height - 1] =
result.width - 1, src.unsafe[src.width - 1, src.height - 1] * 0.25
result.height - 1,
src.getRgbaUnsafe(src.width - 1, src.height - 1) * 0.25
)
# Set src as this result for if we do another power # Set src as this result for if we do another power
src = result src = result
@ -384,7 +385,7 @@ proc magnifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
x += 2 x += 2
for _ in x ..< image.width: for _ in x ..< image.width:
let let
rgbx = image.getRgbaUnsafe(x, y) rgbx = image.unsafe[x, y]
resultIdx = result.dataIndex(x * scale, y * scale) resultIdx = result.dataIndex(x * scale, y * scale)
for i in 0 ..< scale: for i in 0 ..< scale:
result.data[resultIdx + i] = rgbx result.data[resultIdx + i] = rgbx
@ -547,10 +548,10 @@ proc blur*(
for xx in x - radius ..< min(x + radius, 0): for xx in x - radius ..< min(x + radius, 0):
values += outOfBounds * kernel[xx - x + radius] values += outOfBounds * kernel[xx - x + radius]
for xx in max(x - radius, 0) .. min(x + radius, image.width - 1): 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: for xx in max(x - radius, image.width) .. x + radius:
values += outOfBounds * kernel[xx - 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. # Blur in the Y direction.
for y in 0 ..< image.height: for y in 0 ..< image.height:
@ -559,10 +560,10 @@ proc blur*(
for yy in y - radius ..< min(y + radius, 0): for yy in y - radius ..< min(y + radius, 0):
values += outOfBounds * kernel[yy - y + radius] values += outOfBounds * kernel[yy - y + radius]
for yy in max(y - radius, 0) .. min(y + radius, image.height - 1): 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: for yy in max(y - radius, image.height) .. y + radius:
values += outOfBounds * kernel[yy - 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].} = proc newMask*(image: Image): Mask {.raises: [PixieError].} =
## Returns a new mask using the alpha values of the image. ## Returns a new mask using the alpha values of the image.
@ -611,10 +612,10 @@ proc getRgbaSmooth*(
var x0y0, x1y0, x0y1, x1y1: ColorRGBX var x0y0, x1y0, x0y1, x1y1: ColorRGBX
if wrapped: if wrapped:
x0y0 = image.getRgbaUnsafe(x0 mod image.width, y0 mod image.height) x0y0 = image.unsafe[x0 mod image.width, y0 mod image.height]
x1y0 = image.getRgbaUnsafe(x1 mod image.width, y0 mod image.height) x1y0 = image.unsafe[x1 mod image.width, y0 mod image.height]
x0y1 = image.getRgbaUnsafe(x0 mod image.width, y1 mod image.height) x0y1 = image.unsafe[x0 mod image.width, y1 mod image.height]
x1y1 = image.getRgbaUnsafe(x1 mod image.width, y1 mod image.height) x1y1 = image.unsafe[x1 mod image.width, y1 mod image.height]
else: else:
x0y0 = image[x0, y0] x0y0 = image[x0, y0]
x1y0 = image[x1, y0] x1y0 = image[x1, y0]
@ -677,7 +678,7 @@ proc drawCorrect(
yFloat = samplePos.y - h yFloat = samplePos.y - h
when type(a) is Image: when type(a) is Image:
let backdrop = a.getRgbaUnsafe(x, y) let backdrop = a.unsafe[x, y]
when type(b) is Image: when type(b) is Image:
let let
sample = b.getRgbaSmooth(xFloat, yFloat, tiled) sample = b.getRgbaSmooth(xFloat, yFloat, tiled)
@ -686,7 +687,7 @@ proc drawCorrect(
let let
sample = b.getValueSmooth(xFloat, yFloat) sample = b.getValueSmooth(xFloat, yFloat)
blended = blender(backdrop, rgbx(0, 0, 0, sample)) blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.setRgbaUnsafe(x, y, blended) a.unsafe[x, y] = blended
else: # a is a Mask else: # a is a Mask
let backdrop = a.getValueUnsafe(x, y) let backdrop = a.getValueUnsafe(x, y)
when type(b) is Image: when type(b) is Image:
@ -791,7 +792,7 @@ proc drawUber(
for x in xMin ..< xMax: for x in xMin ..< xMax:
when type(a) is Image: when type(a) is Image:
let backdrop = a.getRgbaUnsafe(x, y) let backdrop = a.unsafe[x, y]
when type(b) is Image: when type(b) is Image:
let let
sample = b.getRgbaSmooth(srcPos.x, srcPos.y) sample = b.getRgbaSmooth(srcPos.x, srcPos.y)
@ -800,7 +801,7 @@ proc drawUber(
let let
sample = b.getValueSmooth(srcPos.x, srcPos.y) sample = b.getValueSmooth(srcPos.x, srcPos.y)
blended = blender(backdrop, rgbx(0, 0, 0, sample)) blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.setRgbaUnsafe(x, y, blended) a.unsafe[x, y] = blended
else: # a is a Mask else: # a is a Mask
let backdrop = a.getValueUnsafe(x, y) let backdrop = a.getValueUnsafe(x, y)
when type(b) is Image: when type(b) is Image:
@ -892,20 +893,20 @@ proc drawUber(
let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32) let samplePos = ivec2((srcPos.x - h).int32, (srcPos.y - h).int32)
when type(a) is Image: when type(a) is Image:
let backdrop = a.getRgbaUnsafe(x, y) let backdrop = a.unsafe[x, y]
when type(b) is Image: when type(b) is Image:
let let
sample = b.getRgbaUnsafe(samplePos.x, samplePos.y) sample = b.unsafe[samplePos.x, samplePos.y]
blended = blender(backdrop, sample) blended = blender(backdrop, sample)
else: # b is a Mask else: # b is a Mask
let let
sample = b.getValueUnsafe(samplePos.x, samplePos.y) sample = b.getValueUnsafe(samplePos.x, samplePos.y)
blended = blender(backdrop, rgbx(0, 0, 0, sample)) blended = blender(backdrop, rgbx(0, 0, 0, sample))
a.setRgbaUnsafe(x, y, blended) a.unsafe[x, y] = blended
else: # a is a Mask else: # a is a Mask
let backdrop = a.getValueUnsafe(x, y) let backdrop = a.getValueUnsafe(x, y)
when type(b) is Image: 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 else: # b is a Mask
let sample = b.getValueUnsafe(samplePos.x, samplePos.y) let sample = b.getValueUnsafe(samplePos.x, samplePos.y)
a.setValueUnsafe(x, y, masker(backdrop, sample)) a.setValueUnsafe(x, y, masker(backdrop, sample))

View file

@ -142,7 +142,7 @@ proc fillGradientLinear(image: Image, paint: Paint) =
t = toLineSpace(at, to, xy) t = toLineSpace(at, to, xy)
rgbx = paint.gradientColor(t) rgbx = paint.gradientColor(t)
for y in 0 ..< image.height: for y in 0 ..< image.height:
image.setRgbaUnsafe(x, y, rgbx) image.unsafe[x, y] = rgbx
inc x inc x
elif at.x == to.x: # Vertical gradient elif at.x == to.x: # Vertical gradient
@ -158,7 +158,7 @@ proc fillGradientLinear(image: Image, paint: Paint) =
mm_storeu_si128(image.data[image.dataIndex(x, y)].addr, colorVec) mm_storeu_si128(image.data[image.dataIndex(x, y)].addr, colorVec)
x += 4 x += 4
for x in x ..< image.width: for x in x ..< image.width:
image.setRgbaUnsafe(x, y, rgbx) image.unsafe[x, y] = rgbx
else: else:
for y in 0 ..< image.height: for y in 0 ..< image.height:
@ -166,7 +166,7 @@ proc fillGradientLinear(image: Image, paint: Paint) =
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.setRgbaUnsafe(x, y, paint.gradientColor(t)) image.unsafe[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.
@ -197,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.setRgbaUnsafe(x, y, paint.gradientColor(t)) image.unsafe[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.
@ -223,7 +223,7 @@ proc fillGradientAngular(image: Image, paint: Paint) =
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 + f32PI / 2).fixAngle() / 2 / f32PI + 0.5.float32 t = (angle + gradientAngle + f32PI / 2).fixAngle() / 2 / f32PI + 0.5.float32
image.setRgbaUnsafe(x, y, paint.gradientColor(t)) image.unsafe[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.

View file

@ -1400,7 +1400,7 @@ proc fillCoverage(
if coverage != 0 or blendMode == bmExcludeMask: if coverage != 0 or blendMode == bmExcludeMask:
if blendMode == bmNormal and coverage == 255 and rgbx.a == 255: if blendMode == bmNormal and coverage == 255 and rgbx.a == 255:
# Skip blending # Skip blending
image.setRgbaUnsafe(x, y, rgbx) image.unsafe[x, y] = rgbx
else: else:
var source = rgbx var source = rgbx
if coverage != 255: if coverage != 255:
@ -1408,10 +1408,10 @@ proc fillCoverage(
source.g = ((source.g.uint32 * coverage) div 255).uint8 source.g = ((source.g.uint32 * coverage) div 255).uint8
source.b = ((source.b.uint32 * coverage) div 255).uint8 source.b = ((source.b.uint32 * coverage) div 255).uint8
source.a = ((source.a.uint32 * coverage) div 255).uint8 source.a = ((source.a.uint32 * coverage) div 255).uint8
let backdrop = image.getRgbaUnsafe(x, y) let backdrop = image.unsafe[x, y]
image.setRgbaUnsafe(x, y, blender(backdrop, source)) image.unsafe[x, y] = blender(backdrop, source)
elif blendMode == bmMask: elif blendMode == bmMask:
image.setRgbaUnsafe(x, y, rgbx(0, 0, 0, 0)) image.unsafe[x, y] = rgbx(0, 0, 0, 0)
inc x inc x
if blendMode == bmMask: if blendMode == bmMask:
@ -1515,8 +1515,8 @@ proc fillHits(
x += 4 x += 4
for x in x ..< fillStart + fillLen: for x in x ..< fillStart + fillLen:
let backdrop = image.getRgbaUnsafe(x, y) let backdrop = image.unsafe[x, y]
image.setRgbaUnsafe(x, y, blender(backdrop, rgbx)) image.unsafe[x, y] = blender(backdrop, rgbx)
if blendMode == bmMask: if blendMode == bmMask:
image.clearUnsafe(0, y, startX, y) image.clearUnsafe(0, y, startX, y)