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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ bindings/generated
dump.txt
tests/fileformats/jpeg/generated
tests/fileformats/jpeg/diffs
*.dylib
76 changes: 11 additions & 65 deletions src/pixie/blends.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
## Blending modes.

import chroma, common, internal, std/math

when defined(amd64) and allowSimd:
import nimsimd/sse2
import chroma, common, simd, std/math

# See https://www.w3.org/TR/compositing-1/
# See https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt
Expand Down Expand Up @@ -273,67 +270,16 @@ proc blendSoftLight*(backdrop, source: ColorRGBX): ColorRGBX =
backdrop = backdrop.rgba()
source = source.rgba()

var rgba: ColorRGBA
when defined(amd64) and allowSimd:
let
vb = mm_setr_ps(
backdrop.r.float32,
backdrop.g.float32,
backdrop.b.float32,
0
)
vs = mm_setr_ps(source.r.float32, source.g.float32, source.b.float32, 0)
v2 = mm_set1_ps(2)
v255 = mm_set1_ps(255)
v255sq = mm_set1_ps(255 * 255)
vm = ((v255 - v2 * vs) * vb * vb) / v255sq + (v2 * vs * vb) / v255
values = cast[array[4, uint32]](mm_cvtps_epi32(vm))

rgba.r = values[0].uint8
rgba.g = values[1].uint8
rgba.b = values[2].uint8

# proc alphaFix(backdrop, source, mixed: ColorRGBX): ColorRGBX {.inline.} =
# if backdrop.a == 0 and source.a == 0:
# return
# let
# vb = mm_setr_ps(backdrop.r.float32, backdrop.g.float32, backdrop.b.float32, 0)
# vs = mm_setr_ps(source.r.float32, source.g.float32, source.b.float32, 0)
# vm = mm_setr_ps(mixed.r.float32, mixed.g.float32, mixed.b.float32, 0)
# alphaFix(backdrop, source, vb, vs, vm)

let
sa = source.a.float32
ba = backdrop.a.float32
a = sa + ba * (255 - sa) / 255
if a == 0:
return

let
t0 = mm_set1_ps(sa * (255 - ba))
t1 = mm_set1_ps(sa * ba)
t2 = mm_set1_ps((255 - sa) * ba)
va = mm_set1_ps(a)
final = cast[array[4, uint32]](
mm_cvtps_epi32((t0 * vs + t1 * vm + t2 * vb) / va / v255)
)

rgba.r = final[0].uint8
rgba.g = final[1].uint8
rgba.b = final[2].uint8
rgba.a = a.uint8
else:
let
b = backdrop.color
s = source.color
var blended: Color
blended.r = softLight(b.r, s.r)
blended.g = softLight(b.g, s.g)
blended.b = softLight(b.b, s.b)
blended = alphaFix(b, s, blended)
rgba = blended.rgba

result = rgba.rgbx()
let
b = backdrop.color
s = source.color
var blended: Color
blended.r = softLight(b.r, s.r)
blended.g = softLight(b.g, s.g)
blended.b = softLight(b.b, s.b)
blended = alphaFix(b, s, blended)

result = blended.rgbx()

proc blendHardLight*(backdrop, source: ColorRGBX): ColorRGBX =
result.r = hardLight(backdrop.r, backdrop.a, source.r, source.a)
Expand Down
30 changes: 30 additions & 0 deletions src/pixie/common.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,36 @@ type
ImageDimensions* = object
width*, height*: int

Image* = ref object
## Image object that holds bitmap data in premultiplied alpha RGBA format.
width*, height*: int
data*: seq[ColorRGBX]

Mask* = ref object
## Mask object that holds mask opacity data.
width*, height*: int
data*: seq[uint8]

proc newImage*(width, height: int): Image {.raises: [PixieError].} =
## Creates a new image with the parameter dimensions.
if width <= 0 or height <= 0:
raise newException(PixieError, "Image width and height must be > 0")

result = Image()
result.width = width
result.height = height
result.data = newSeq[ColorRGBX](width * height)

proc newMask*(width, height: int): Mask {.raises: [PixieError].} =
## Creates a new mask with the parameter dimensions.
if width <= 0 or height <= 0:
raise newException(PixieError, "Mask width and height must be > 0")

result = Mask()
result.width = width
result.height = height
result.data = newSeq[uint8](width * height)

proc mix*(a, b: uint8, t: float32): uint8 {.inline, raises: [].} =
## Linearly interpolate between a and b using t.
let t = round(t * 255).uint32
Expand Down
5 changes: 1 addition & 4 deletions src/pixie/fileformats/jpeg.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import chroma, flatty/binny, pixie/common, pixie/images, pixie/internal,
pixie/masks, std/decls, std/sequtils, std/strutils

when defined(amd64) and allowSimd:
import nimsimd/sse2
pixie/masks, pixie/simd, std/decls, std/sequtils, std/strutils

# This JPEG decoder is loosely based on stb_image which is public domain.

Expand Down
5 changes: 1 addition & 4 deletions src/pixie/fileformats/png.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import chroma, flatty/binny, math, pixie/common, pixie/images, pixie/internal,
pixie/masks, zippy, zippy/crc

when defined(amd64) and allowSimd:
import nimsimd/sse2
pixie/simd, zippy, zippy/crc

# See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html

Expand Down
90 changes: 22 additions & 68 deletions src/pixie/images.nim
Original file line number Diff line number Diff line change
@@ -1,45 +1,26 @@
import blends, bumpy, chroma, common, internal, masks, vmath
import blends, bumpy, chroma, common, internal, masks, simd, vmath

when allowSimd:
import simd

when defined(amd64):
import nimsimd/sse2
export Image, newImage

const h = 0.5.float32

type
Image* = ref object
## Image object that holds bitmap data in RGBA format.
width*, height*: int
data*: seq[ColorRGBX]

UnsafeImage = distinct Image
type UnsafeImage = distinct Image

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

proc newImage*(width, height: int): Image {.raises: [PixieError].} =
## Creates a new image with the parameter dimensions.
if width <= 0 or height <= 0:
raise newException(PixieError, "Image width and height must be > 0")

result = Image()
result.width = width
result.height = height
result.data = newSeq[ColorRGBX](width * height)

proc newImage*(mask: Mask): Image {.raises: [PixieError].} =
proc newImage*(mask: Mask): Image {.hasSimd, raises: [PixieError].} =
result = newImage(mask.width, mask.height)

when allowSimd and compiles(newImageFromMaskSimd):
newImageFromMaskSimd(result.data, mask.data)
return

for i in 0 ..< mask.data.len:
let v = mask.data[i]
result.data[i] = rgbx(v, v, v, v)

proc newMask*(image: Image): Mask {.hasSimd, raises: [PixieError].} =
## Returns a new mask using the alpha values of the image.
result = newMask(image.width, image.height)
for i in 0 ..< image.data.len:
result.data[i] = image.data[i].a

proc copy*(image: Image): Image {.raises: [PixieError].} =
## Copies the image data into a new image.
result = newImage(image.width, image.height)
Expand Down Expand Up @@ -95,25 +76,17 @@ proc fill*(image: Image, color: SomeColor) {.inline, raises: [].} =
## Fills the image with the color.
fillUnsafe(image.data, color, 0, image.data.len)

proc isOneColor*(image: Image): bool {.raises: [].} =
proc isOneColor*(image: Image): bool {.hasSimd, raises: [].} =
## Checks if the entire image is the same color.
when allowSimd and compiles(isOneColorSimd):
return isOneColorSimd(image.data)

result = true

let color = cast[uint32](image.data[0])
for i in 0 ..< image.data.len:
if cast[uint32](image.data[i]) != color:
return false

proc isTransparent*(image: Image): bool {.raises: [].} =
proc isTransparent*(image: Image): bool {.hasSimd, raises: [].} =
## Checks if this image is fully transparent or not.
when allowSimd and compiles(isTransparentSimd):
return isTransparentSimd(image.data)

result = true

for i in 0 ..< image.data.len:
if image.data[i].a != 0:
return false
Expand Down Expand Up @@ -347,46 +320,38 @@ proc magnifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
result.width * 4
)

proc applyOpacity*(image: Image, opacity: float32) {.raises: [].} =
proc applyOpacity*(target: Image, opacity: float32) {.hasSimd, raises: [].} =
## Multiplies alpha of the image by opacity.
let opacity = round(255 * opacity).uint16
if opacity == 255:
return

if opacity == 0:
image.fill(rgbx(0, 0, 0, 0))
return

when allowSimd and compiles(applyOpacitySimd):
applyOpacitySimd(image.data, opacity)
target.fill(rgbx(0, 0, 0, 0))
return

for i in 0 ..< image.data.len:
var rgbx = image.data[i]
for i in 0 ..< target.data.len:
var rgbx = target.data[i]
rgbx.r = ((rgbx.r * opacity) div 255).uint8
rgbx.g = ((rgbx.g * opacity) div 255).uint8
rgbx.b = ((rgbx.b * opacity) div 255).uint8
rgbx.a = ((rgbx.a * opacity) div 255).uint8
image.data[i] = rgbx
target.data[i] = rgbx

proc invert*(image: Image) {.raises: [].} =
proc invert*(target: Image) {.hasSimd, raises: [].} =
## Inverts all of the colors and alpha.
when allowSimd and compiles(invertImageSimd):
invertImageSimd(image.data)
return

for i in 0 ..< image.data.len:
var rgbx = image.data[i]
for i in 0 ..< target.data.len:
var rgbx = target.data[i]
rgbx.r = 255 - rgbx.r
rgbx.g = 255 - rgbx.g
rgbx.b = 255 - rgbx.b
rgbx.a = 255 - rgbx.a
image.data[i] = rgbx
target.data[i] = rgbx

# Inverting rgbx(50, 100, 150, 200) becomes rgbx(205, 155, 105, 55). This
# is not a valid premultiplied alpha color.
# We need to convert back to premultiplied alpha after inverting.
image.data.toPremultipliedAlpha()
target.data.toPremultipliedAlpha()

proc blur*(
image: Image, radius: float32, outOfBounds: SomeColor = color(0, 0, 0, 0)
Expand Down Expand Up @@ -449,17 +414,6 @@ proc blur*(
values += outOfBounds * kernel[yy - y + radius]
image.unsafe[x, y] = rgbx(values)

proc newMask*(image: Image): Mask {.raises: [PixieError].} =
## Returns a new mask using the alpha values of the image.
result = newMask(image.width, image.height)

when allowSimd and compiles(newMaskFromImageSimd):
newMaskFromImageSimd(result.data, image.data)
return

for i in 0 ..< image.data.len:
result.data[i] = image.data[i].a

proc getRgbaSmooth*(
image: Image, x, y: float32, wrapped = false
): ColorRGBX {.raises: [].} =
Expand Down
45 changes: 8 additions & 37 deletions src/pixie/internal.nim
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import bumpy, chroma, common, system/memory, vmath

const allowSimd* = not defined(pixieNoSimd) and not defined(tcc)

when allowSimd:
import simd

when defined(amd64):
import nimsimd/sse2
import bumpy, chroma, common, simd, system/memory, vmath

template currentExceptionAsPixieError*(): untyped =
## Gets the current exception and returns it as a PixieError with stack trace.
Expand Down Expand Up @@ -76,21 +68,16 @@ proc fillUnsafe*(

proc fillUnsafe*(
data: var seq[ColorRGBX], color: SomeColor, start, len: int
) {.raises: [].} =
) {.hasSimd, raises: [].} =
## Fills the image data with the color starting at index start and
## continuing for len indices.
when allowSimd and compiles(fillUnsafeSimd):
fillUnsafeSimd(data, start, len, color)
return

let rgbx = color.asRgbx()

# Use memset when every byte has the same value
if rgbx.r == rgbx.g and rgbx.r == rgbx.b and rgbx.r == rgbx.a:
nimSetMem(data[start].addr, rgbx.r.cint, len * 4)
else:
for color in data.mitems:
color = rgbx
for i in start ..< start + len:
data[i] = rgbx

const straightAlphaTable = block:
var table: array[256, array[256, uint8]]
Expand All @@ -110,12 +97,10 @@ proc toStraightAlpha*(data: var seq[ColorRGBA | ColorRGBX]) {.raises: [].} =
c.b = straightAlphaTable[c.a][c.b]
data[i] = c

proc toPremultipliedAlpha*(data: var seq[ColorRGBA | ColorRGBX]) {.raises: [].} =
proc toPremultipliedAlpha*(
data: var seq[ColorRGBA | ColorRGBX]
) {.hasSimd, raises: [].} =
## Converts an image to premultiplied alpha from straight alpha.
when allowSimd and compiles(toPremultipliedAlphaSimd):
toPremultipliedAlphaSimd(data)
return

for i in 0 ..< data.len:
var c = data[i]
if c.a != 255:
Expand All @@ -124,25 +109,11 @@ proc toPremultipliedAlpha*(data: var seq[ColorRGBA | ColorRGBX]) {.raises: [].}
c.b = ((c.b.uint32 * c.a) div 255).uint8
data[i] = c

proc isOpaque*(data: var seq[ColorRGBX], start, len: int): bool =
when allowSimd and compiles(isOpaqueSimd):
return isOpaqueSimd(data, start, len)

proc isOpaque*(data: var seq[ColorRGBX], start, len: int): bool {.hasSimd.} =
result = true

for i in start ..< start + len:
if data[i].a != 255:
return false

when defined(amd64) and allowSimd:
proc applyOpacity*(color: M128, opacity: float32): ColorRGBX {.inline.} =
let opacityVec = mm_set1_ps(opacity)
var finalColor = mm_cvtps_epi32(mm_mul_ps(color, opacityVec))
finalColor = mm_packus_epi16(finalColor, mm_setzero_si128())
finalColor = mm_packus_epi16(finalColor, mm_setzero_si128())
cast[ColorRGBX](mm_cvtsi128_si32(finalColor))

export pack4xAlphaValues, unpackAlphaValues

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