Merge pull request #262 from guzba/master
raises pragma to generate binding error handling
This commit is contained in:
commit
5e97339725
17 changed files with 689 additions and 492 deletions
|
@ -10,15 +10,15 @@ type
|
|||
FileFormat* = enum
|
||||
ffPng, ffBmp, ffJpg, ffGif
|
||||
|
||||
converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline.} =
|
||||
## Convert a paremultiplied alpha RGBA to a straight alpha RGBA.
|
||||
converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline, raises: [].} =
|
||||
## Convert a premultiplied alpha RGBA to a straight alpha RGBA.
|
||||
c.rgba()
|
||||
|
||||
converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline.} =
|
||||
converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline, raises: [].} =
|
||||
## Convert a straight alpha RGBA to a premultiplied alpha RGBA.
|
||||
c.rgbx()
|
||||
|
||||
proc decodeImage*(data: string | seq[uint8]): Image =
|
||||
proc decodeImage*(data: string | seq[uint8]): Image {.raises: [PixieError].} =
|
||||
## Loads an image from memory.
|
||||
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
|
||||
decodePng(data)
|
||||
|
@ -34,22 +34,28 @@ proc decodeImage*(data: string | seq[uint8]): Image =
|
|||
else:
|
||||
raise newException(PixieError, "Unsupported image file format")
|
||||
|
||||
proc decodeMask*(data: string | seq[uint8]): Mask =
|
||||
proc decodeMask*(data: string | seq[uint8]): Mask {.raises: [PixieError].} =
|
||||
## Loads a mask from memory.
|
||||
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
|
||||
newMask(decodePng(data))
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported mask file format")
|
||||
|
||||
proc readImage*(filePath: string): Image =
|
||||
proc readImage*(filePath: string): Image {.inline, raises: [PixieError].} =
|
||||
## Loads an image from a file.
|
||||
decodeImage(readFile(filePath))
|
||||
try:
|
||||
decodeImage(readFile(filePath))
|
||||
except IOError as e:
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc readMask*(filePath: string): Mask =
|
||||
proc readMask*(filePath: string): Mask {.raises: [PixieError].} =
|
||||
## Loads a mask from a file.
|
||||
decodeMask(readFile(filePath))
|
||||
try:
|
||||
decodeMask(readFile(filePath))
|
||||
except IOError as e:
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc encodeImage*(image: Image, fileFormat: FileFormat): string =
|
||||
proc encodeImage*(image: Image, fileFormat: FileFormat): string {.raises: [PixieError].} =
|
||||
## Encodes an image into memory.
|
||||
case fileFormat:
|
||||
of ffPng:
|
||||
|
@ -61,7 +67,7 @@ proc encodeImage*(image: Image, fileFormat: FileFormat): string =
|
|||
of ffGif:
|
||||
raise newException(PixieError, "Unsupported file format")
|
||||
|
||||
proc encodeMask*(mask: Mask, fileFormat: FileFormat): string =
|
||||
proc encodeMask*(mask: Mask, fileFormat: FileFormat): string {.raises: [PixieError].} =
|
||||
## Encodes a mask into memory.
|
||||
case fileFormat:
|
||||
of ffPng:
|
||||
|
@ -69,7 +75,7 @@ proc encodeMask*(mask: Mask, fileFormat: FileFormat): string =
|
|||
else:
|
||||
raise newException(PixieError, "Unsupported file format")
|
||||
|
||||
proc writeFile*(image: Image, filePath: string) =
|
||||
proc writeFile*(image: Image, filePath: string) {.raises: [PixieError].} =
|
||||
## Writes an image to a file.
|
||||
let fileFormat = case splitFile(filePath).ext.toLowerAscii():
|
||||
of ".png": ffPng
|
||||
|
@ -77,9 +83,13 @@ proc writeFile*(image: Image, filePath: string) =
|
|||
of ".jpg", ".jpeg": ffJpg
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported file extension")
|
||||
writeFile(filePath, image.encodeImage(fileFormat))
|
||||
|
||||
proc writeFile*(mask: Mask, filePath: string) =
|
||||
try:
|
||||
writeFile(filePath, image.encodeImage(fileFormat))
|
||||
except IOError as e:
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc writeFile*(mask: Mask, filePath: string) {.raises: [PixieError].} =
|
||||
## Writes a mask to a file.
|
||||
let fileFormat = case splitFile(filePath).ext.toLowerAscii():
|
||||
of ".png": ffPng
|
||||
|
@ -87,4 +97,8 @@ proc writeFile*(mask: Mask, filePath: string) =
|
|||
of ".jpg", ".jpeg": ffJpg
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported file extension")
|
||||
writeFile(filePath, mask.encodeMask(fileFormat))
|
||||
|
||||
try:
|
||||
writeFile(filePath, mask.encodeMask(fileFormat))
|
||||
except IOError as e:
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
|
|
@ -34,18 +34,18 @@ type
|
|||
bmSubtractMask ## Inverse mask
|
||||
bmExcludeMask
|
||||
|
||||
Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX
|
||||
Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].}
|
||||
## Function signature returned by blender.
|
||||
Masker* = proc(backdrop, source: uint8): uint8
|
||||
Masker* = proc(backdrop, source: uint8): uint8 {.raises: [].}
|
||||
## Function signature returned by masker.
|
||||
|
||||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc min(a, b: uint32): uint32 {.inline.} =
|
||||
proc min(a, b: uint32): uint32 {.inline, raises: [].} =
|
||||
if a < b: a else: b
|
||||
|
||||
proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA =
|
||||
proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA {.raises: [].} =
|
||||
## After mixing an image, adjust its alpha value to be correct.
|
||||
let
|
||||
sa = source.a.uint32
|
||||
|
@ -68,7 +68,7 @@ proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA =
|
|||
result.b = (b div a div 255).uint8
|
||||
result.a = a.uint8
|
||||
|
||||
proc alphaFix(backdrop, source, mixed: Color): Color =
|
||||
proc alphaFix(backdrop, source, mixed: Color): Color {.raises: [].} =
|
||||
## After mixing an image, adjust its alpha value to be correct.
|
||||
result.a = (source.a + backdrop.a * (1.0 - source.a))
|
||||
if result.a == 0:
|
||||
|
@ -87,16 +87,16 @@ proc alphaFix(backdrop, source, mixed: Color): Color =
|
|||
result.g /= result.a
|
||||
result.b /= result.a
|
||||
|
||||
proc blendAlpha*(backdrop, source: uint8): uint8 {.inline.} =
|
||||
proc blendAlpha*(backdrop, source: uint8): uint8 {.inline, raises: [].} =
|
||||
## Blends alphas of backdrop, source.
|
||||
source + ((backdrop.uint32 * (255 - source)) div 255).uint8
|
||||
|
||||
proc screen(backdrop, source: uint32): uint8 {.inline.} =
|
||||
proc screen(backdrop, source: uint32): uint8 {.inline, raises: [].} =
|
||||
((backdrop + source).int32 - ((backdrop * source) div 255).int32).uint8
|
||||
|
||||
proc hardLight(
|
||||
backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint32
|
||||
): uint8 {.inline.} =
|
||||
): uint8 {.inline, raises: [].} =
|
||||
if sourceColor * 2 <= sourceAlpha:
|
||||
((
|
||||
2 * sourceColor * backdropColor +
|
||||
|
@ -106,41 +106,41 @@ proc hardLight(
|
|||
else:
|
||||
screen(backdropColor, sourceColor)
|
||||
|
||||
proc softLight(backdrop, source: float32): float32 {.inline.} =
|
||||
proc softLight(backdrop, source: float32): float32 {.inline, raises: [].} =
|
||||
## Pegtop
|
||||
(1 - 2 * source) * backdrop ^ 2 + 2 * source * backdrop
|
||||
|
||||
proc `+`(c: Color, v: float32): Color {.inline.} =
|
||||
proc `+`(c: Color, v: float32): Color {.inline, raises: [].} =
|
||||
result.r = c.r + v
|
||||
result.g = c.g + v
|
||||
result.b = c.b + v
|
||||
result.a = c.a + v
|
||||
|
||||
proc `+`(v: float32, c: Color): Color {.inline.} =
|
||||
proc `+`(v: float32, c: Color): Color {.inline, raises: [].} =
|
||||
c + v
|
||||
|
||||
proc `*`(c: Color, v: float32): Color {.inline.} =
|
||||
proc `*`(c: Color, v: float32): Color {.inline, raises: [].} =
|
||||
result.r = c.r * v
|
||||
result.g = c.g * v
|
||||
result.b = c.b * v
|
||||
result.a = c.a * v
|
||||
|
||||
proc `/`(c: Color, v: float32): Color {.inline.} =
|
||||
proc `/`(c: Color, v: float32): Color {.inline, raises: [].} =
|
||||
result.r = c.r / v
|
||||
result.g = c.g / v
|
||||
result.b = c.b / v
|
||||
result.a = c.a / v
|
||||
|
||||
proc `-`(c: Color, v: float32): Color {.inline.} =
|
||||
proc `-`(c: Color, v: float32): Color {.inline, raises: [].} =
|
||||
result.r = c.r - v
|
||||
result.g = c.g - v
|
||||
result.b = c.b - v
|
||||
result.a = c.a - v
|
||||
|
||||
proc Lum(C: Color): float32 {.inline.} =
|
||||
proc Lum(C: Color): float32 {.inline, raises: [].} =
|
||||
0.3 * C.r + 0.59 * C.g + 0.11 * C.b
|
||||
|
||||
proc ClipColor(C: var Color) {.inline.} =
|
||||
proc ClipColor(C: var Color) {.inline, raises: [].} =
|
||||
let
|
||||
L = Lum(C)
|
||||
n = min([C.r, C.g, C.b])
|
||||
|
@ -150,22 +150,22 @@ proc ClipColor(C: var Color) {.inline.} =
|
|||
if x > 1:
|
||||
C = L + (((C - L) * (1 - L)) / (x - L))
|
||||
|
||||
proc SetLum(C: Color, l: float32): Color {.inline.} =
|
||||
proc SetLum(C: Color, l: float32): Color {.inline, raises: [].} =
|
||||
let d = l - Lum(C)
|
||||
result.r = C.r + d
|
||||
result.g = C.g + d
|
||||
result.b = C.b + d
|
||||
ClipColor(result)
|
||||
|
||||
proc Sat(C: Color): float32 {.inline.} =
|
||||
proc Sat(C: Color): float32 {.inline, raises: [].} =
|
||||
max([C.r, C.g, C.b]) - min([C.r, C.g, C.b])
|
||||
|
||||
proc SetSat(C: Color, s: float32): Color {.inline.} =
|
||||
proc SetSat(C: Color, s: float32): Color {.inline, raises: [].} =
|
||||
let satC = Sat(C)
|
||||
if satC > 0:
|
||||
result = (C - min([C.r, C.g, C.b])) * s / satC
|
||||
|
||||
proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
if backdrop.a == 0:
|
||||
return source
|
||||
if source.a == 255:
|
||||
|
@ -179,7 +179,7 @@ proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
result.b = source.b + ((backdrop.b.uint32 * k) div 255).uint8
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
proc blend(
|
||||
backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
|
||||
): uint8 {.inline.} =
|
||||
|
@ -193,7 +193,7 @@ proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
proc blend(
|
||||
backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
|
||||
): uint8 {.inline.} =
|
||||
|
@ -218,7 +218,7 @@ proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
# result = alphaFix(backdrop, source, result)
|
||||
# result = result.toPremultipliedAlpha()
|
||||
|
||||
proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let
|
||||
backdrop = backdrop.rgba()
|
||||
source = source.rgba()
|
||||
|
@ -235,7 +235,7 @@ proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
blended.b = blend(backdrop.b, source.b)
|
||||
result = alphaFix(backdrop, source, blended).rgbx()
|
||||
|
||||
proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
proc blend(
|
||||
backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
|
||||
): uint8 {.inline.} =
|
||||
|
@ -249,7 +249,7 @@ proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
result.r = screen(backdrop.r, source.r)
|
||||
result.g = screen(backdrop.g, source.g)
|
||||
result.b = screen(backdrop.b, source.b)
|
||||
|
@ -265,7 +265,7 @@ proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
# result = alphaFix(backdrop, source, result)
|
||||
# result = result.toPremultipliedAlpha()
|
||||
|
||||
proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let
|
||||
backdrop = backdrop.rgba()
|
||||
source = source.rgba()
|
||||
|
@ -282,13 +282,13 @@ proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
blended.b = blend(backdrop.b, source.b)
|
||||
result = alphaFix(backdrop, source, blended).rgbx()
|
||||
|
||||
proc blendOverlay(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendOverlay(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
result.r = hardLight(source.r, source.a, backdrop.r, backdrop.a)
|
||||
result.g = hardLight(source.g, source.a, backdrop.g, backdrop.a)
|
||||
result.b = hardLight(source.b, source.a, backdrop.b, backdrop.a)
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
# proc softLight(backdrop, source: int32): uint8 {.inline.} =
|
||||
# ## Pegtop
|
||||
# (
|
||||
|
@ -362,13 +362,13 @@ proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
|
||||
result = rgba.rgbx()
|
||||
|
||||
proc blendHardLight(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendHardLight(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
result.r = hardLight(backdrop.r, backdrop.a, source.r, source.a)
|
||||
result.g = hardLight(backdrop.g, backdrop.a, source.g, source.a)
|
||||
result.b = hardLight(backdrop.b, backdrop.a, source.b, source.a)
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
proc blend(
|
||||
backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
|
||||
): uint8 {.inline.} =
|
||||
|
@ -384,7 +384,7 @@ proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
proc blend(backdrop, source: uint32): uint8 {.inline.} =
|
||||
let v = (backdrop + source).int32 - ((2 * backdrop * source) div 255).int32
|
||||
max(0, v).uint8
|
||||
|
@ -393,63 +393,63 @@ proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX =
|
|||
result.b = blend(backdrop.b.uint32, source.b.uint32)
|
||||
result.a = blendAlpha(backdrop.a, source.a)
|
||||
|
||||
proc blendColor(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendColor(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let
|
||||
backdrop = backdrop.rgba().color
|
||||
source = source.rgba().color
|
||||
blended = SetLum(source, Lum(backdrop))
|
||||
result = alphaFix(backdrop, source, blended).rgba.rgbx()
|
||||
|
||||
proc blendLuminosity(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendLuminosity(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let
|
||||
backdrop = backdrop.rgba().color
|
||||
source = source.rgba().color
|
||||
blended = SetLum(backdrop, Lum(source))
|
||||
result = alphaFix(backdrop, source, blended).rgba.rgbx()
|
||||
|
||||
proc blendHue(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendHue(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let
|
||||
backdrop = backdrop.rgba().color
|
||||
source = source.rgba().color
|
||||
blended = SetLum(SetSat(source, Sat(backdrop)), Lum(backdrop))
|
||||
result = alphaFix(backdrop, source, blended).rgba.rgbx()
|
||||
|
||||
proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let
|
||||
backdrop = backdrop.rgba().color
|
||||
source = source.rgba().color
|
||||
blended = SetLum(SetSat(backdrop, Sat(source)), Lum(backdrop))
|
||||
result = alphaFix(backdrop, source, blended).rgba.rgbx()
|
||||
|
||||
proc blendMask(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendMask(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let k = source.a.uint32
|
||||
result.r = ((backdrop.r * k) div 255).uint8
|
||||
result.g = ((backdrop.g * k) div 255).uint8
|
||||
result.b = ((backdrop.b * k) div 255).uint8
|
||||
result.a = ((backdrop.a * k) div 255).uint8
|
||||
|
||||
proc blendSubtractMask(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendSubtractMask(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let a = (backdrop.a.uint32 * (255 - source.a)) div 255
|
||||
result.r = ((backdrop.r * a) div 255).uint8
|
||||
result.g = ((backdrop.g * a) div 255).uint8
|
||||
result.b = ((backdrop.b * a) div 255).uint8
|
||||
result.a = a.uint8
|
||||
|
||||
proc blendExcludeMask(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendExcludeMask(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
let a = max(backdrop.a, source.a).uint32 - min(backdrop.a, source.a)
|
||||
result.r = ((source.r * a) div 255).uint8
|
||||
result.g = ((source.g * a) div 255).uint8
|
||||
result.b = ((source.b * a) div 255).uint8
|
||||
result.a = a.uint8
|
||||
|
||||
proc blendOverwrite(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
proc blendOverwrite(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} =
|
||||
source
|
||||
|
||||
# proc blendWhite(backdrop, source: ColorRGBX): ColorRGBX =
|
||||
# ## For testing
|
||||
# rgbx(255, 255, 255, 255)
|
||||
|
||||
proc blender*(blendMode: BlendMode): Blender =
|
||||
proc blender*(blendMode: BlendMode): Blender {.raises: [].} =
|
||||
## Returns a blend function for a given blend mode.
|
||||
case blendMode:
|
||||
of bmNormal: blendNormal
|
||||
|
@ -475,24 +475,24 @@ proc blender*(blendMode: BlendMode): Blender =
|
|||
of bmSubtractMask: blendSubtractMask
|
||||
of bmExcludeMask: blendExcludeMask
|
||||
|
||||
proc maskNormal(backdrop, source: uint8): uint8 =
|
||||
proc maskNormal(backdrop, source: uint8): uint8 {.raises: [].} =
|
||||
## Blending masks
|
||||
blendAlpha(backdrop, source)
|
||||
|
||||
proc maskMask(backdrop, source: uint8): uint8 =
|
||||
proc maskMask(backdrop, source: uint8): uint8 {.raises: [].} =
|
||||
## Masking masks
|
||||
((backdrop.uint32 * source) div 255).uint8
|
||||
|
||||
proc maskSubtract(backdrop, source: uint8): uint8 =
|
||||
proc maskSubtract(backdrop, source: uint8): uint8 {.raises: [].} =
|
||||
((backdrop.uint32 * (255 - source)) div 255).uint8
|
||||
|
||||
proc maskExclude(backdrop, source: uint8): uint8 =
|
||||
proc maskExclude(backdrop, source: uint8): uint8 {.raises: [].} =
|
||||
max(backdrop, source) - min(backdrop, source)
|
||||
|
||||
proc maskOverwrite(backdrop, source: uint8): uint8 =
|
||||
proc maskOverwrite(backdrop, source: uint8): uint8 {.raises: [].} =
|
||||
source
|
||||
|
||||
proc masker*(blendMode: BlendMode): Masker =
|
||||
proc masker*(blendMode: BlendMode): Masker {.raises: [PixieError].} =
|
||||
## Returns a blend masking function for a given blend masking mode.
|
||||
case blendMode:
|
||||
of bmNormal: maskNormal
|
||||
|
@ -507,12 +507,12 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
import nimsimd/sse2
|
||||
|
||||
type
|
||||
BlenderSimd* = proc(blackdrop, source: M128i): M128i
|
||||
BlenderSimd* = proc(blackdrop, source: M128i): M128i {.raises: [].}
|
||||
## Function signature returned by blenderSimd.
|
||||
MaskerSimd* = proc(blackdrop, source: M128i): M128i
|
||||
MaskerSimd* = proc(blackdrop, source: M128i): M128i {.raises: [].}
|
||||
## Function signature returned by maskerSimd.
|
||||
|
||||
proc blendNormalSimd(backdrop, source: M128i): M128i =
|
||||
proc blendNormalSimd(backdrop, source: M128i): M128i {.raises: [].} =
|
||||
let
|
||||
alphaMask = mm_set1_epi32(cast[int32](0xff000000))
|
||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||
|
@ -541,7 +541,7 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
|
||||
)
|
||||
|
||||
proc blendMaskSimd(backdrop, source: M128i): M128i =
|
||||
proc blendMaskSimd(backdrop, source: M128i): M128i {.raises: [].} =
|
||||
let
|
||||
alphaMask = mm_set1_epi32(cast[int32](0xff000000))
|
||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||
|
@ -562,10 +562,10 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
|
||||
mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
|
||||
|
||||
proc blendOverwriteSimd(backdrop, source: M128i): M128i =
|
||||
proc blendOverwriteSimd(backdrop, source: M128i): M128i {.raises: [].} =
|
||||
source
|
||||
|
||||
proc blenderSimd*(blendMode: BlendMode): BlenderSimd =
|
||||
proc blenderSimd*(blendMode: BlendMode): BlenderSimd {.raises: [PixieError].} =
|
||||
## Returns a blend function for a given blend mode with SIMD support.
|
||||
case blendMode:
|
||||
of bmNormal: blendNormalSimd
|
||||
|
@ -574,11 +574,11 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
else:
|
||||
raise newException(PixieError, "No SIMD blender for " & $blendMode)
|
||||
|
||||
proc hasSimdBlender*(blendMode: BlendMode): bool =
|
||||
proc hasSimdBlender*(blendMode: BlendMode): bool {.inline, raises: [].} =
|
||||
## Is there a blend function for a given blend mode with SIMD support?
|
||||
blendMode in {bmNormal, bmMask, bmOverwrite}
|
||||
|
||||
proc maskNormalSimd(backdrop, source: M128i): M128i =
|
||||
proc maskNormalSimd(backdrop, source: M128i): M128i {.raises: [].} =
|
||||
## Blending masks
|
||||
let
|
||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||
|
@ -615,7 +615,7 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
|
||||
mm_or_si128(blendedEven, mm_slli_epi16(blendedOdd, 8))
|
||||
|
||||
proc maskMaskSimd(backdrop, source: M128i): M128i =
|
||||
proc maskMaskSimd(backdrop, source: M128i): M128i {.raises: [].} =
|
||||
let
|
||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||
div255 = mm_set1_epi16(cast[int16](0x8081))
|
||||
|
@ -636,7 +636,7 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
|
||||
mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
|
||||
|
||||
proc maskerSimd*(blendMode: BlendMode): MaskerSimd =
|
||||
proc maskerSimd*(blendMode: BlendMode): MaskerSimd {.raises: [PixieError].} =
|
||||
## Returns a blend masking function with SIMD support.
|
||||
case blendMode:
|
||||
of bmNormal: maskNormalSimd
|
||||
|
@ -645,7 +645,7 @@ when defined(amd64) and not defined(pixieNoSimd):
|
|||
else:
|
||||
raise newException(PixieError, "No SIMD masker for " & $blendMode)
|
||||
|
||||
proc hasSimdMasker*(blendMode: BlendMode): bool =
|
||||
proc hasSimdMasker*(blendMode: BlendMode): bool {.inline, raises: [].} =
|
||||
## Is there a blend masking function with SIMD support?
|
||||
blendMode in {bmNormal, bmMask, bmOverwrite}
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ import bumpy, chroma, vmath
|
|||
type
|
||||
PixieError* = object of ValueError ## Raised if an operation fails.
|
||||
|
||||
proc lerp*(a, b: uint8, t: float32): uint8 {.inline.} =
|
||||
proc lerp*(a, b: uint8, t: float32): uint8 {.inline, raises: [].} =
|
||||
## Linearly interpolate between a and b using t.
|
||||
let t = round(t * 255).uint32
|
||||
((a * (255 - t) + b * t) div 255).uint8
|
||||
|
||||
proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline.} =
|
||||
proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline, raises: [].} =
|
||||
## Linearly interpolate between a and b using t.
|
||||
let x = round(t * 255).uint32
|
||||
result.r = ((a.r.uint32 * (255 - x) + b.r.uint32 * x) div 255).uint8
|
||||
|
@ -16,14 +16,14 @@ proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline.} =
|
|||
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 lerp*(a, b: Color, v: float32): Color {.inline.} =
|
||||
proc lerp*(a, b: Color, v: float32): Color {.inline, raises: [].} =
|
||||
## Linearly interpolate between a and b using t.
|
||||
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 snapToPixels*(rect: Rect): Rect =
|
||||
proc snapToPixels*(rect: Rect): Rect {.raises: [].} =
|
||||
let
|
||||
xMin = rect.x
|
||||
xMax = rect.x + rect.w
|
||||
|
|
|
@ -44,7 +44,7 @@ type
|
|||
TextMetrics* = object
|
||||
width*: float32
|
||||
|
||||
proc newContext*(image: Image): Context =
|
||||
proc newContext*(image: Image): Context {.raises: [].} =
|
||||
## Create a new Context that will draw to the parameter image.
|
||||
result = Context()
|
||||
result.image = image
|
||||
|
@ -53,15 +53,17 @@ proc newContext*(image: Image): Context =
|
|||
result.globalAlpha = 1
|
||||
result.lineWidth = 1
|
||||
result.miterLimit = 10
|
||||
result.fillStyle = rgbx(0, 0, 0, 255)
|
||||
result.strokeStyle = rgbx(0, 0, 0, 255)
|
||||
result.fillStyle = newPaint(pkSolid)
|
||||
result.fillStyle.color = color(0, 0, 0, 1)
|
||||
result.strokeStyle = newPaint(pkSolid)
|
||||
result.strokeStyle.color = color(0, 0, 0, 1)
|
||||
result.fontSize = 12
|
||||
|
||||
proc newContext*(width, height: int): Context {.inline.} =
|
||||
proc newContext*(width, height: int): Context {.inline, raises: [PixieError].} =
|
||||
## Create a new Context that will draw to a new image of width and height.
|
||||
newContext(newImage(width, height))
|
||||
|
||||
proc state(ctx: Context): ContextState =
|
||||
proc state(ctx: Context): ContextState {.raises: [PixieError].} =
|
||||
result.fillStyle = ctx.fillStyle
|
||||
result.strokeStyle = ctx.strokeStyle
|
||||
result.globalAlpha = ctx.globalAlpha
|
||||
|
@ -76,7 +78,7 @@ proc state(ctx: Context): ContextState =
|
|||
result.mat = ctx.mat
|
||||
result.mask = if ctx.mask != nil: ctx.mask.copy() else: nil
|
||||
|
||||
proc save*(ctx: Context) {.inline.} =
|
||||
proc save*(ctx: Context) {.inline, raises: [PixieError].} =
|
||||
## Saves the entire state of the context by pushing the current state onto
|
||||
## a stack.
|
||||
ctx.stateStack.add(ctx.state())
|
||||
|
@ -84,7 +86,7 @@ proc save*(ctx: Context) {.inline.} =
|
|||
ctx.fillStyle = newPaint(ctx.fillStyle)
|
||||
ctx.strokeStyle = newPaint(ctx.strokeStyle)
|
||||
|
||||
proc saveLayer*(ctx: Context) =
|
||||
proc saveLayer*(ctx: Context) {.raises: [PixieError].} =
|
||||
## Saves the entire state of the context by pushing the current state onto
|
||||
## a stack and allocates a new image layer for subsequent drawing. Calling
|
||||
## restore blends the current layer image onto the prior layer or root image.
|
||||
|
@ -93,7 +95,7 @@ proc saveLayer*(ctx: Context) =
|
|||
ctx.stateStack.add(state)
|
||||
ctx.layer = newImage(ctx.image.width, ctx.image.height)
|
||||
|
||||
proc restore*(ctx: Context) =
|
||||
proc restore*(ctx: Context) {.raises: [PixieError].} =
|
||||
## Restores the most recently saved context state by popping the top entry
|
||||
## in the drawing state stack. If there is no saved state, this method does
|
||||
## nothing.
|
||||
|
@ -128,7 +130,9 @@ proc restore*(ctx: Context) =
|
|||
else: # Otherwise draw to the root image
|
||||
ctx.image.draw(poppedLayer)
|
||||
|
||||
proc fill(ctx: Context, image: Image, path: Path, windingRule: WindingRule) =
|
||||
proc fill(
|
||||
ctx: Context, image: Image, path: Path, windingRule: WindingRule
|
||||
) {.raises: [PixieError].} =
|
||||
var image = image
|
||||
|
||||
if ctx.globalAlpha != 1:
|
||||
|
@ -146,7 +150,7 @@ proc fill(ctx: Context, image: Image, path: Path, windingRule: WindingRule) =
|
|||
ctx.layer.applyOpacity(ctx.globalAlpha)
|
||||
ctx.restore()
|
||||
|
||||
proc stroke(ctx: Context, image: Image, path: Path) =
|
||||
proc stroke(ctx: Context, image: Image, path: Path) {.raises: [PixieError].} =
|
||||
var image = image
|
||||
|
||||
if ctx.globalAlpha != 1:
|
||||
|
@ -168,17 +172,19 @@ proc stroke(ctx: Context, image: Image, path: Path) =
|
|||
ctx.layer.applyOpacity(ctx.globalAlpha)
|
||||
ctx.restore()
|
||||
|
||||
proc newFont(ctx: Context): Font =
|
||||
proc newFont(ctx: Context): Font {.raises: [PixieError].} =
|
||||
if ctx.font == "":
|
||||
raise newException(PixieError, "No font has been set on this Context")
|
||||
|
||||
if ctx.font notin ctx.typefaces:
|
||||
ctx.typefaces[ctx.font] = readTypeface(ctx.font)
|
||||
|
||||
result = newFont(ctx.typefaces[ctx.font])
|
||||
result = newFont(ctx.typefaces.getOrDefault(ctx.font, nil))
|
||||
result.size = ctx.fontSize
|
||||
|
||||
proc fillText(ctx: Context, image: Image, text: string, at: Vec2) =
|
||||
proc fillText(
|
||||
ctx: Context, image: Image, text: string, at: Vec2
|
||||
) {.raises: [PixieError].} =
|
||||
let font = newFont(ctx)
|
||||
|
||||
# Canvas positions text relative to the alphabetic baseline by default
|
||||
|
@ -204,7 +210,9 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) =
|
|||
ctx.layer.applyOpacity(ctx.globalAlpha)
|
||||
ctx.restore()
|
||||
|
||||
proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) =
|
||||
proc strokeText(
|
||||
ctx: Context, image: Image, text: string, at: Vec2
|
||||
) {.raises: [PixieError].} =
|
||||
let font = newFont(ctx)
|
||||
|
||||
# Canvas positions text relative to the alphabetic baseline by default
|
||||
|
@ -235,29 +243,29 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) =
|
|||
ctx.layer.applyOpacity(ctx.globalAlpha)
|
||||
ctx.restore()
|
||||
|
||||
proc beginPath*(ctx: Context) {.inline.} =
|
||||
proc beginPath*(ctx: Context) {.inline, raises: [].} =
|
||||
## Starts a new path by emptying the list of sub-paths.
|
||||
ctx.path = newPath()
|
||||
|
||||
proc moveTo*(ctx: Context, v: Vec2) {.inline.} =
|
||||
proc moveTo*(ctx: Context, v: Vec2) {.inline, raises: [].} =
|
||||
## Begins a new sub-path at the point (x, y).
|
||||
ctx.path.moveTo(v)
|
||||
|
||||
proc moveTo*(ctx: Context, x, y: float32) {.inline.} =
|
||||
proc moveTo*(ctx: Context, x, y: float32) {.inline, raises: [].} =
|
||||
## Begins a new sub-path at the point (x, y).
|
||||
ctx.moveTo(vec2(x, y))
|
||||
|
||||
proc lineTo*(ctx: Context, v: Vec2) {.inline.} =
|
||||
proc lineTo*(ctx: Context, v: Vec2) {.inline, raises: [].} =
|
||||
## Adds a straight line to the current sub-path by connecting the sub-path's
|
||||
## last point to the specified (x, y) coordinates.
|
||||
ctx.path.lineTo(v)
|
||||
|
||||
proc lineTo*(ctx: Context, x, y: float32) {.inline.} =
|
||||
proc lineTo*(ctx: Context, x, y: float32) {.inline, raises: [].} =
|
||||
## Adds a straight line to the current sub-path by connecting the sub-path's
|
||||
## last point to the specified (x, y) coordinates.
|
||||
ctx.lineTo(vec2(x, y))
|
||||
|
||||
proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline.} =
|
||||
proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline, raises: [].} =
|
||||
## Adds a cubic Bézier curve to the current sub-path. It requires three
|
||||
## points: the first two are control points and the third one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
|
@ -266,14 +274,16 @@ proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline.} =
|
|||
|
||||
proc bezierCurveTo*(
|
||||
ctx: Context, cp1x, cp1y, cp2x, cp2y, x, y: float32
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a cubic Bézier curve to the current sub-path. It requires three
|
||||
## points: the first two are control points and the third one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
## which can be changed using moveTo() before creating the Bézier curve.
|
||||
ctx.bezierCurveTo(vec2(cp1x, cp1y), vec2(cp2x, cp2y), vec2(x, y))
|
||||
|
||||
proc quadraticCurveTo*(ctx: Context, cpx, cpy, x, y: float32) {.inline.} =
|
||||
proc quadraticCurveTo*(
|
||||
ctx: Context, cpx, cpy, x, y: float32
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a quadratic Bézier curve to the current sub-path. It requires two
|
||||
## points: the first one is a control point and the second one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
|
@ -281,7 +291,9 @@ proc quadraticCurveTo*(ctx: Context, cpx, cpy, x, y: float32) {.inline.} =
|
|||
## Bézier curve.
|
||||
ctx.path.quadraticCurveTo(cpx, cpy, x, y)
|
||||
|
||||
proc quadraticCurveTo*(ctx: Context, ctrl, to: Vec2) {.inline.} =
|
||||
proc quadraticCurveTo*(
|
||||
ctx: Context, ctrl, to: Vec2
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a quadratic Bézier curve to the current sub-path. It requires two
|
||||
## points: the first one is a control point and the second one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
|
@ -289,45 +301,55 @@ proc quadraticCurveTo*(ctx: Context, ctrl, to: Vec2) {.inline.} =
|
|||
## Bézier curve.
|
||||
ctx.path.quadraticCurveTo(ctrl, to)
|
||||
|
||||
proc arc*(ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false) =
|
||||
proc arc*(
|
||||
ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a circular arc.
|
||||
ctx.path.arc(x, y, r, a0, a1, ccw)
|
||||
|
||||
proc arc*(ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) =
|
||||
proc arc*(
|
||||
ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false
|
||||
) {.raises: [PixieError].} =
|
||||
## Adds a circular arc to the current sub-path.
|
||||
ctx.path.arc(pos, r, a, ccw)
|
||||
|
||||
proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) =
|
||||
proc arcTo*(
|
||||
ctx: Context, x1, y1, x2, y2, radius: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a circular arc using the given control points and radius.
|
||||
ctx.path.arcTo(x1, y1, x2, y2, radius)
|
||||
|
||||
proc arcTo*(ctx: Context, a, b: Vec2, r: float32) =
|
||||
proc arcTo*(
|
||||
ctx: Context, a, b: Vec2, r: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Adds a circular arc using the given control points and radius.
|
||||
ctx.path.arcTo(a, b, r)
|
||||
|
||||
proc closePath*(ctx: Context) {.inline.} =
|
||||
proc closePath*(ctx: Context) {.inline, raises: [].} =
|
||||
## Attempts to add a straight line from the current point to the start of
|
||||
## the current sub-path. If the shape has already been closed or has only
|
||||
## one point, this function does nothing.
|
||||
ctx.path.closePath()
|
||||
|
||||
proc rect*(ctx: Context, rect: Rect) {.inline.} =
|
||||
proc rect*(ctx: Context, rect: Rect) {.inline, raises: [].} =
|
||||
## Adds a rectangle to the current path.
|
||||
ctx.path.rect(rect)
|
||||
|
||||
proc rect*(ctx: Context, x, y, width, height: float32) {.inline.} =
|
||||
proc rect*(ctx: Context, x, y, width, height: float32) {.inline, raises: [].} =
|
||||
## Adds a rectangle to the current path.
|
||||
ctx.path.rect(x, y, width, height)
|
||||
|
||||
proc ellipse*(ctx: Context, center: Vec2, rx, ry: float32) {.inline.} =
|
||||
proc ellipse*(ctx: Context, center: Vec2, rx, ry: float32) {.inline, raises: [].} =
|
||||
## Adds an ellipse to the current sub-path.
|
||||
ctx.path.ellipse(center, rx, ry)
|
||||
|
||||
proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline.} =
|
||||
proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline, raises: [].} =
|
||||
## Adds an ellipse to the current sub-path.
|
||||
ctx.path.ellipse(x, y, rx, ry)
|
||||
|
||||
proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) =
|
||||
proc fill*(
|
||||
ctx: Context, path: Path, windingRule = wrNonZero
|
||||
) {.raises: [PixieError].} =
|
||||
## Fills the path with the current fillStyle.
|
||||
if ctx.mask != nil and ctx.layer == nil:
|
||||
ctx.saveLayer()
|
||||
|
@ -338,13 +360,17 @@ proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) =
|
|||
else:
|
||||
ctx.fill(ctx.image, path, windingRule)
|
||||
|
||||
proc fill*(ctx: Context, windingRule = wrNonZero) {.inline.} =
|
||||
proc fill*(
|
||||
ctx: Context, windingRule = wrNonZero
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Fills the current path with the current fillStyle.
|
||||
ctx.fill(ctx.path, windingRule)
|
||||
|
||||
proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.}
|
||||
proc clip*(ctx: Context, windingRule = wrNonZero) {.inline, raises: [PixieError].}
|
||||
|
||||
proc clip*(ctx: Context, path: Path, windingRule = wrNonZero) =
|
||||
proc clip*(
|
||||
ctx: Context, path: Path, windingRule = wrNonZero
|
||||
) {.raises: [PixieError].} =
|
||||
## Turns the path into the current clipping region. The previous clipping
|
||||
## region, if any, is intersected with the current or given path to create
|
||||
## the new clipping region.
|
||||
|
@ -354,15 +380,17 @@ proc clip*(ctx: Context, path: Path, windingRule = wrNonZero) =
|
|||
else:
|
||||
ctx.mask.fillPath(path, windingRule = windingRule, blendMode = bmMask)
|
||||
|
||||
proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.} =
|
||||
proc clip*(
|
||||
ctx: Context, windingRule = wrNonZero
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Turns the current path into the current clipping region. The previous
|
||||
## clipping region, if any, is intersected with the current or given path
|
||||
## to create the new clipping region.
|
||||
ctx.clip(ctx.path, windingRule)
|
||||
|
||||
proc stroke*(ctx: Context) {.inline.}
|
||||
proc stroke*(ctx: Context) {.inline, raises: [PixieError].}
|
||||
|
||||
proc stroke*(ctx: Context, path: Path) =
|
||||
proc stroke*(ctx: Context, path: Path) {.raises: [PixieError].} =
|
||||
## Strokes (outlines) the current or given path with the current strokeStyle.
|
||||
if ctx.mask != nil and ctx.layer == nil:
|
||||
ctx.saveLayer()
|
||||
|
@ -373,11 +401,11 @@ proc stroke*(ctx: Context, path: Path) =
|
|||
else:
|
||||
ctx.stroke(ctx.image, path)
|
||||
|
||||
proc stroke*(ctx: Context) {.inline.} =
|
||||
proc stroke*(ctx: Context) {.inline, raises: [PixieError].} =
|
||||
## Strokes (outlines) the current or given path with the current strokeStyle.
|
||||
ctx.stroke(ctx.path)
|
||||
|
||||
proc clearRect*(ctx: Context, rect: Rect) =
|
||||
proc clearRect*(ctx: Context, rect: Rect) {.raises: [PixieError].} =
|
||||
## Erases the pixels in a rectangular area.
|
||||
let paint = newPaint(pkSolid)
|
||||
paint.blendMode = bmOverwrite
|
||||
|
@ -385,37 +413,43 @@ proc clearRect*(ctx: Context, rect: Rect) =
|
|||
let path = newPath()
|
||||
path.rect(rect)
|
||||
if ctx.layer != nil:
|
||||
ctx.layer.fillPath( path, paint, ctx.mat)
|
||||
ctx.layer.fillPath(path, paint, ctx.mat)
|
||||
else:
|
||||
ctx.image.fillPath(path, paint, ctx.mat)
|
||||
|
||||
proc clearRect*(ctx: Context, x, y, width, height: float32) {.inline.} =
|
||||
proc clearRect*(
|
||||
ctx: Context, x, y, width, height: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Erases the pixels in a rectangular area.
|
||||
ctx.clearRect(rect(x, y, width, height))
|
||||
|
||||
proc fillRect*(ctx: Context, rect: Rect) =
|
||||
proc fillRect*(ctx: Context, rect: Rect) {.raises: [PixieError].} =
|
||||
## Draws a rectangle that is filled according to the current fillStyle.
|
||||
let path = newPath()
|
||||
path.rect(rect)
|
||||
ctx.fill(path)
|
||||
|
||||
proc fillRect*(ctx: Context, x, y, width, height: float32) {.inline.} =
|
||||
proc fillRect*(
|
||||
ctx: Context, x, y, width, height: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a rectangle that is filled according to the current fillStyle.
|
||||
ctx.fillRect(rect(x, y, width, height))
|
||||
|
||||
proc strokeRect*(ctx: Context, rect: Rect) =
|
||||
proc strokeRect*(ctx: Context, rect: Rect) {.raises: [PixieError].} =
|
||||
## Draws a rectangle that is stroked (outlined) according to the current
|
||||
## strokeStyle and other context settings.
|
||||
let path = newPath()
|
||||
path.rect(rect)
|
||||
ctx.stroke(path)
|
||||
|
||||
proc strokeRect*(ctx: Context, x, y, width, height: float32) {.inline.} =
|
||||
proc strokeRect*(
|
||||
ctx: Context, x, y, width, height: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a rectangle that is stroked (outlined) according to the current
|
||||
## strokeStyle and other context settings.
|
||||
ctx.strokeRect(rect(x, y, width, height))
|
||||
|
||||
proc fillText*(ctx: Context, text: string, at: Vec2) =
|
||||
proc fillText*(ctx: Context, text: string, at: Vec2) {.raises: [PixieError].} =
|
||||
## Draws a text string at the specified coordinates, filling the string's
|
||||
## characters with the current fillStyle
|
||||
if ctx.mask != nil and ctx.layer == nil:
|
||||
|
@ -427,12 +461,14 @@ proc fillText*(ctx: Context, text: string, at: Vec2) =
|
|||
else:
|
||||
ctx.fillText(ctx.image, text, at)
|
||||
|
||||
proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} =
|
||||
proc fillText*(
|
||||
ctx: Context, text: string, x, y: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws the outlines of the characters of a text string at the specified
|
||||
## coordinates.
|
||||
ctx.fillText(text, vec2(x, y))
|
||||
|
||||
proc strokeText*(ctx: Context, text: string, at: Vec2) =
|
||||
proc strokeText*(ctx: Context, text: string, at: Vec2) {.raises: [PixieError].} =
|
||||
## Draws the outlines of the characters of a text string at the specified
|
||||
## coordinates.
|
||||
if ctx.mask != nil and ctx.layer == nil:
|
||||
|
@ -444,12 +480,14 @@ proc strokeText*(ctx: Context, text: string, at: Vec2) =
|
|||
else:
|
||||
ctx.strokeText(ctx.image, text, at)
|
||||
|
||||
proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} =
|
||||
proc strokeText*(
|
||||
ctx: Context, text: string, x, y: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws the outlines of the characters of a text string at the specified
|
||||
## coordinates.
|
||||
ctx.strokeText(text, vec2(x, y))
|
||||
|
||||
proc measureText*(ctx: Context, text: string): TextMetrics =
|
||||
proc measureText*(ctx: Context, text: string): TextMetrics {.raises: [PixieError].} =
|
||||
## Returns a TextMetrics object that contains information about the measured
|
||||
## text (such as its width, for example).
|
||||
let
|
||||
|
@ -457,61 +495,63 @@ proc measureText*(ctx: Context, text: string): TextMetrics =
|
|||
bounds = typeset(font, text).computeBounds()
|
||||
result.width = bounds.x
|
||||
|
||||
proc getLineDash*(ctx: Context): seq[float32] {.inline.} =
|
||||
proc getLineDash*(ctx: Context): seq[float32] {.inline, raises: [].} =
|
||||
ctx.lineDash
|
||||
|
||||
proc setLineDash*(ctx: Context, lineDash: seq[float32]) {.inline.} =
|
||||
proc setLineDash*(ctx: Context, lineDash: seq[float32]) {.inline, raises: [].} =
|
||||
ctx.lineDash = lineDash
|
||||
|
||||
proc getTransform*(ctx: Context): Mat3 {.inline.} =
|
||||
proc getTransform*(ctx: Context): Mat3 {.inline, raises: []} =
|
||||
## Retrieves the current transform matrix being applied to the context.
|
||||
ctx.mat
|
||||
|
||||
proc setTransform*(ctx: Context, transform: Mat3) {.inline.} =
|
||||
proc setTransform*(ctx: Context, transform: Mat3) {.inline, raises: [].} =
|
||||
## Overrides the transform matrix being applied to the context.
|
||||
ctx.mat = transform
|
||||
|
||||
proc setTransform*(ctx: Context, a, b, c, d, e, f: float32) {.inline.} =
|
||||
proc setTransform*(ctx: Context, a, b, c, d, e, f: float32) {.inline, raises: [].} =
|
||||
## Overrides the transform matrix being applied to the context.
|
||||
ctx.mat = mat3(a, b, 0, c, d, 0, e, f, 1)
|
||||
|
||||
proc transform*(ctx: Context, transform: Mat3) {.inline.} =
|
||||
proc transform*(ctx: Context, transform: Mat3) {.inline, raises: [].} =
|
||||
## Multiplies the current transform with the matrix described by the
|
||||
## arguments of this method.
|
||||
ctx.mat = ctx.mat * transform
|
||||
|
||||
proc transform*(ctx: Context, a, b, c, d, e, f: float32) {.inline.} =
|
||||
proc transform*(ctx: Context, a, b, c, d, e, f: float32) {.inline, raises: [].} =
|
||||
## Multiplies the current transform with the matrix described by the
|
||||
## arguments of this method.
|
||||
ctx.transform(mat3(a, b, 0, c, d, 0, e, f, 1))
|
||||
|
||||
proc translate*(ctx: Context, v: Vec2) {.inline.} =
|
||||
proc translate*(ctx: Context, v: Vec2) {.inline, raises: [].} =
|
||||
## Adds a translation transformation to the current matrix.
|
||||
ctx.mat = ctx.mat * translate(v)
|
||||
|
||||
proc translate*(ctx: Context, x, y: float32) {.inline.} =
|
||||
proc translate*(ctx: Context, x, y: float32) {.inline, raises: [].} =
|
||||
## Adds a translation transformation to the current matrix.
|
||||
ctx.mat = ctx.mat * translate(vec2(x, y))
|
||||
|
||||
proc scale*(ctx: Context, v: Vec2) {.inline.} =
|
||||
proc scale*(ctx: Context, v: Vec2) {.inline, raises: [].} =
|
||||
## Adds a scaling transformation to the context units horizontally and/or
|
||||
## vertically.
|
||||
ctx.mat = ctx.mat * scale(v)
|
||||
|
||||
proc scale*(ctx: Context, x, y: float32) {.inline.} =
|
||||
proc scale*(ctx: Context, x, y: float32) {.inline, raises: [].} =
|
||||
## Adds a scaling transformation to the context units horizontally and/or
|
||||
## vertically.
|
||||
ctx.mat = ctx.mat * scale(vec2(x, y))
|
||||
|
||||
proc rotate*(ctx: Context, angle: float32) {.inline.} =
|
||||
proc rotate*(ctx: Context, angle: float32) {.inline, raises: [].} =
|
||||
## Adds a rotation to the transformation matrix.
|
||||
ctx.mat = ctx.mat * rotate(-angle)
|
||||
|
||||
proc resetTransform*(ctx: Context) {.inline.} =
|
||||
proc resetTransform*(ctx: Context) {.inline, raises: [].} =
|
||||
## Resets the current transform to the identity matrix.
|
||||
ctx.mat = mat3()
|
||||
|
||||
proc drawImage*(ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32) =
|
||||
proc drawImage*(
|
||||
ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a source image onto the destination image.
|
||||
let
|
||||
imageMat = ctx.mat * translate(vec2(dx, dy)) * scale(vec2(
|
||||
|
@ -530,15 +570,17 @@ proc drawImage*(ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32) =
|
|||
|
||||
ctx.fillStyle = savedFillStyle
|
||||
|
||||
proc drawImage*(ctx: Context, image: Image, dx, dy: float32) =
|
||||
proc drawImage*(
|
||||
ctx: Context, image: Image, dx, dy: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a source image onto the destination image.
|
||||
ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32)
|
||||
|
||||
proc drawImage*(ctx: Context, image: Image, pos: Vec2) =
|
||||
proc drawImage*(ctx: Context, image: Image, pos: Vec2) {.raises: [PixieError].} =
|
||||
## Draws a source image onto the destination image.
|
||||
ctx.drawImage(image, pos.x, pos.y)
|
||||
|
||||
proc drawImage*(ctx: Context, image: Image, rect: Rect) =
|
||||
proc drawImage*(ctx: Context, image: Image, rect: Rect) {.raises: [PixieError].} =
|
||||
## Draws a source image onto the destination image.
|
||||
ctx.drawImage(image, rect.x, rect.y, rect.w, rect.h)
|
||||
|
||||
|
@ -547,12 +589,12 @@ proc drawImage*(
|
|||
image: Image,
|
||||
sx, sy, sWidth, sHeight,
|
||||
dx, dy, dWidth, dHeight: float32
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a source image onto the destination image.
|
||||
let image = image.subImage(sx.int, sy.int, sWidth.int, sHeight.int)
|
||||
ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32)
|
||||
|
||||
proc drawImage*(ctx: Context, image: Image, src, dest: Rect) =
|
||||
proc drawImage*(ctx: Context, image: Image, src, dest: Rect) {.raises: [PixieError].} =
|
||||
## Draws a source image onto the destination image.
|
||||
ctx.drawImage(
|
||||
image,
|
||||
|
@ -562,29 +604,31 @@ proc drawImage*(ctx: Context, image: Image, src, dest: Rect) =
|
|||
|
||||
proc isPointInPath*(
|
||||
ctx: Context, path: Path, pos: Vec2, windingRule = wrNonZero
|
||||
): bool =
|
||||
): bool {.raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is contained in the current path.
|
||||
path.fillOverlaps(pos, ctx.mat, windingRule)
|
||||
|
||||
proc isPointInPath*(
|
||||
ctx: Context, path: Path, x, y: float32, windingRule = wrNonZero
|
||||
): bool {.inline.} =
|
||||
): bool {.inline, raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is contained in the current path.
|
||||
ctx.isPointInPath(path, vec2(x, y), windingRule)
|
||||
|
||||
proc isPointInPath*(
|
||||
ctx: Context, pos: Vec2, windingRule = wrNonZero
|
||||
): bool {.inline.} =
|
||||
): bool {.inline, raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is contained in the current path.
|
||||
ctx.isPointInPath(ctx.path, pos, windingRule)
|
||||
|
||||
proc isPointInPath*(
|
||||
ctx: Context, x, y: float32, windingRule = wrNonZero
|
||||
): bool {.inline.} =
|
||||
): bool {.inline, raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is contained in the current path.
|
||||
ctx.isPointInPath(ctx.path, vec2(x, y), windingRule)
|
||||
|
||||
proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool =
|
||||
proc isPointInStroke*(
|
||||
ctx: Context, path: Path, pos: Vec2
|
||||
): bool {.raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is inside the area contained
|
||||
## by the stroking of a path.
|
||||
path.strokeOverlaps(
|
||||
|
@ -597,17 +641,23 @@ proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool =
|
|||
ctx.lineDash
|
||||
)
|
||||
|
||||
proc isPointInStroke*(ctx: Context, path: Path, x, y: float32): bool {.inline.} =
|
||||
proc isPointInStroke*(
|
||||
ctx: Context, path: Path, x, y: float32
|
||||
): bool {.inline, raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is inside the area contained
|
||||
## by the stroking of a path.
|
||||
ctx.isPointInStroke(path, vec2(x, y))
|
||||
|
||||
proc isPointInStroke*(ctx: Context, pos: Vec2): bool {.inline.} =
|
||||
proc isPointInStroke*(
|
||||
ctx: Context, pos: Vec2
|
||||
): bool {.inline, raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is inside the area contained
|
||||
## by the stroking of a path.
|
||||
ctx.isPointInStroke(ctx.path, pos)
|
||||
|
||||
proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} =
|
||||
proc isPointInStroke*(
|
||||
ctx: Context, x, y: float32
|
||||
): bool {.inline, raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is inside the area contained
|
||||
## by the stroking of a path.
|
||||
ctx.isPointInStroke(ctx.path, vec2(x, y))
|
||||
|
@ -616,53 +666,69 @@ proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} =
|
|||
# Additional procs that are not part of the JS API
|
||||
#
|
||||
|
||||
proc roundedRect*(ctx: Context, x, y, w, h, nw, ne, se, sw: float32) {.inline.} =
|
||||
proc roundedRect*(
|
||||
ctx: Context, x, y, w, h, nw, ne, se, sw: float32
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a rounded rectangle to the current path.
|
||||
ctx.path.roundedRect(x, y, w, h, nw, ne, se, sw)
|
||||
|
||||
proc roundedRect*(ctx: Context, rect: Rect, nw, ne, se, sw: float32) {.inline.} =
|
||||
proc roundedRect*(
|
||||
ctx: Context, rect: Rect, nw, ne, se, sw: float32
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a rounded rectangle to the current path.
|
||||
ctx.path.roundedRect(rect, nw, ne, se, sw)
|
||||
|
||||
proc circle*(ctx: Context, cx, cy, r: float32) {.inline.} =
|
||||
proc circle*(ctx: Context, cx, cy, r: float32) {.inline, raises: [].} =
|
||||
## Adds a circle to the current path.
|
||||
ctx.path.circle(cx, cy, r)
|
||||
|
||||
proc circle*(ctx: Context, circle: Circle) {.inline.} =
|
||||
proc circle*(ctx: Context, circle: Circle) {.inline, raises: [].} =
|
||||
## Adds a circle to the current path.
|
||||
ctx.path.circle(circle)
|
||||
|
||||
proc polygon*(ctx: Context, x, y, size: float32, sides: int) {.inline.} =
|
||||
proc polygon*(
|
||||
ctx: Context, x, y, size: float32, sides: int
|
||||
) {.inline, raises: [].} =
|
||||
## Adds an n-sided regular polygon at (x, y) of size to the current path.
|
||||
ctx.path.polygon(x, y, size, sides)
|
||||
|
||||
proc polygon*(ctx: Context, pos: Vec2, size: float32, sides: int) {.inline.} =
|
||||
proc polygon*(
|
||||
ctx: Context, pos: Vec2, size: float32, sides: int
|
||||
) {.inline, raises: [].} =
|
||||
## Adds an n-sided regular polygon at (x, y) of size to the current path.
|
||||
ctx.path.polygon(pos, size, sides)
|
||||
|
||||
proc fillRoundedRect*(ctx: Context, rect: Rect, nw, ne, se, sw: float32) =
|
||||
proc fillRoundedRect*(
|
||||
ctx: Context, rect: Rect, nw, ne, se, sw: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a rounded rectangle that is filled according to the current fillStyle.
|
||||
let path = newPath()
|
||||
path.roundedRect(rect, nw, ne, se, sw)
|
||||
ctx.fill(path)
|
||||
|
||||
proc fillRoundedRect*(ctx: Context, rect: Rect, radius: float32) {.inline.} =
|
||||
proc fillRoundedRect*(
|
||||
ctx: Context, rect: Rect, radius: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a rounded rectangle that is filled according to the current fillStyle.
|
||||
ctx.fillRoundedRect(rect, radius, radius, radius, radius)
|
||||
|
||||
proc strokeRoundedRect*(ctx: Context, rect: Rect, nw, ne, se, sw: float32) =
|
||||
proc strokeRoundedRect*(
|
||||
ctx: Context, rect: Rect, nw, ne, se, sw: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a rounded rectangle that is stroked (outlined) according to the
|
||||
## current strokeStyle and other context settings.
|
||||
let path = newPath()
|
||||
path.roundedRect(rect, nw, ne, se, sw)
|
||||
ctx.stroke(path)
|
||||
|
||||
proc strokeRoundedRect*(ctx: Context, rect: Rect, radius: float32) {.inline.} =
|
||||
proc strokeRoundedRect*(
|
||||
ctx: Context, rect: Rect, radius: float32
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a rounded rectangle that is stroked (outlined) according to the
|
||||
## current strokeStyle and other context settings.
|
||||
ctx.strokeRoundedRect(rect, radius, radius, radius, radius)
|
||||
|
||||
proc strokeSegment*(ctx: Context, segment: Segment) =
|
||||
proc strokeSegment*(ctx: Context, segment: Segment) {.raises: [PixieError].} =
|
||||
## Strokes a segment (draws a line from segment.at to segment.to) according
|
||||
## to the current strokeStyle and other context settings.
|
||||
let path = newPath()
|
||||
|
@ -670,40 +736,52 @@ proc strokeSegment*(ctx: Context, segment: Segment) =
|
|||
path.lineTo(segment.to)
|
||||
ctx.stroke(path)
|
||||
|
||||
proc fillEllipse*(ctx: Context, center: Vec2, rx, ry: float32) =
|
||||
proc fillEllipse*(
|
||||
ctx: Context, center: Vec2, rx, ry: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws an ellipse that is filled according to the current fillStyle.
|
||||
let path = newPath()
|
||||
path.ellipse(center, rx, ry)
|
||||
ctx.fill(path)
|
||||
|
||||
proc strokeEllipse*(ctx: Context, center: Vec2, rx, ry: float32) =
|
||||
proc strokeEllipse*(
|
||||
ctx: Context, center: Vec2, rx, ry: float32
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws an ellipse that is stroked (outlined) according to the current
|
||||
## strokeStyle and other context settings.
|
||||
let path = newPath()
|
||||
path.ellipse(center, rx, ry)
|
||||
ctx.stroke(path)
|
||||
|
||||
proc fillCircle*(ctx: Context, circle: Circle) =
|
||||
proc fillCircle*(
|
||||
ctx: Context, circle: Circle
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a circle that is filled according to the current fillStyle
|
||||
let path = newPath()
|
||||
path.circle(circle)
|
||||
ctx.fill(path)
|
||||
|
||||
proc strokeCircle*(ctx: Context, circle: Circle) =
|
||||
proc strokeCircle*(
|
||||
ctx: Context, circle: Circle
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws a circle that is stroked (outlined) according to the current
|
||||
## strokeStyle and other context settings.
|
||||
let path = newPath()
|
||||
path.circle(circle)
|
||||
ctx.stroke(path)
|
||||
|
||||
proc fillPolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) =
|
||||
proc fillPolygon*(
|
||||
ctx: Context, pos: Vec2, size: float32, sides: int
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws an n-sided regular polygon at (x, y) of size that is filled according
|
||||
## to the current fillStyle.
|
||||
let path = newPath()
|
||||
path.polygon(pos, size, sides)
|
||||
ctx.fill(path)
|
||||
|
||||
proc strokePolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) =
|
||||
proc strokePolygon*(
|
||||
ctx: Context, pos: Vec2, size: float32, sides: int
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws an n-sided regular polygon at (x, y) of size that is stroked
|
||||
## (outlined) according to the current strokeStyle and other context settings.
|
||||
let path = newPath()
|
||||
|
|
|
@ -4,7 +4,7 @@ import chroma, flatty/binny, pixie/common, pixie/images
|
|||
|
||||
const bmpSignature* = "BM"
|
||||
|
||||
proc decodeBmp*(data: string): Image =
|
||||
proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
|
||||
## Decodes bitmap data into an Image.
|
||||
|
||||
# BMP Header
|
||||
|
@ -48,11 +48,11 @@ proc decodeBmp*(data: string): Image =
|
|||
offset += 3
|
||||
result[x, result.height - y - 1] = rgba.rgbx()
|
||||
|
||||
proc decodeBmp*(data: seq[uint8]): Image {.inline.} =
|
||||
proc decodeBmp*(data: seq[uint8]): Image {.inline, raises: [PixieError].} =
|
||||
## Decodes bitmap data into an Image.
|
||||
decodeBmp(cast[string](data))
|
||||
|
||||
proc encodeBmp*(image: Image): string =
|
||||
proc encodeBmp*(image: Image): string {.raises: [].} =
|
||||
## Encodes an image into the BMP file format.
|
||||
|
||||
# BMP Header
|
||||
|
|
|
@ -10,7 +10,7 @@ template failInvalid() =
|
|||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc decodeGif*(data: string): Image =
|
||||
proc decodeGif*(data: string): Image {.raises: [PixieError].} =
|
||||
## Decodes GIF data into an Image.
|
||||
|
||||
if data.len <= 13: failInvalid()
|
||||
|
|
|
@ -6,7 +6,7 @@ when defined(pixieUseStb):
|
|||
const
|
||||
jpgStartOfImage* = [0xFF.uint8, 0xD8]
|
||||
|
||||
proc decodeJpg*(data: seq[uint8]): Image =
|
||||
proc decodeJpg*(data: seq[uint8]): Image {.raises: [PixieError].} =
|
||||
## Decodes the JPEG into an Image.
|
||||
when not defined(pixieUseStb):
|
||||
raise newException(PixieError, "Decoding JPG requires -d:pixieUseStb")
|
||||
|
@ -19,10 +19,10 @@ proc decodeJpg*(data: seq[uint8]): Image =
|
|||
result = newImage(width, height)
|
||||
copyMem(result.data[0].addr, pixels[0].unsafeAddr, pixels.len)
|
||||
|
||||
proc decodeJpg*(data: string): Image {.inline.} =
|
||||
proc decodeJpg*(data: string): Image {.inline, raises: [PixieError].} =
|
||||
## Decodes the JPEG data into an Image.
|
||||
decodeJpg(cast[seq[uint8]](data))
|
||||
|
||||
proc encodeJpg*(image: Image): string =
|
||||
proc encodeJpg*(image: Image): string {.raises: [PixieError].} =
|
||||
## Encodes Image into a JPEG data string.
|
||||
raise newException(PixieError, "Encoding JPG not supported yet")
|
||||
|
|
|
@ -23,7 +23,7 @@ template failInvalid() =
|
|||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc decodeHeader(data: string): PngHeader =
|
||||
proc decodeHeader(data: string): PngHeader {.raises: [PixieError].} =
|
||||
result.width = data.readUint32(0).swap().int
|
||||
result.height = data.readUint32(4).swap().int
|
||||
result.bitDepth = data.readUint8(8)
|
||||
|
@ -79,7 +79,7 @@ proc decodeHeader(data: string): PngHeader =
|
|||
if result.interlaceMethod != 0:
|
||||
raise newException(PixieError, "Interlaced PNG not yet supported")
|
||||
|
||||
proc decodePalette(data: string): seq[ColorRGB] =
|
||||
proc decodePalette(data: string): seq[ColorRGB] {.raises: [PixieError].} =
|
||||
if data.len == 0 or data.len mod 3 != 0:
|
||||
failInvalid()
|
||||
|
||||
|
@ -88,7 +88,9 @@ proc decodePalette(data: string): seq[ColorRGB] =
|
|||
for i in 0 ..< data.len div 3:
|
||||
result[i] = cast[ptr ColorRGB](data[i * 3].unsafeAddr)[]
|
||||
|
||||
proc unfilter(uncompressed: string, height, rowBytes, bpp: int): string =
|
||||
proc unfilter(
|
||||
uncompressed: string, height, rowBytes, bpp: int
|
||||
): string {.raises: [].} =
|
||||
result.setLen(uncompressed.len - height)
|
||||
|
||||
template uncompressedIdx(x, y: int): int =
|
||||
|
@ -162,7 +164,7 @@ proc decodeImageData(
|
|||
header: PngHeader,
|
||||
palette: seq[ColorRGB],
|
||||
transparency, data: string
|
||||
): seq[ColorRGBA] =
|
||||
): seq[ColorRGBA] {.raises: [PixieError].} =
|
||||
result.setLen(header.width * header.height)
|
||||
|
||||
let
|
||||
|
@ -314,7 +316,7 @@ proc decodeImageData(
|
|||
else:
|
||||
discard # Not possible, parseHeader validates
|
||||
|
||||
proc decodePng*(data: string): Image =
|
||||
proc decodePng*(data: string): Image {.raises: [PixieError].} =
|
||||
## Decodes the PNG data into an Image.
|
||||
|
||||
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
|
||||
|
@ -424,7 +426,9 @@ proc decodePng*(data: string): Image =
|
|||
result = newImage(header.width, header.height)
|
||||
copyMem(result.data[0].addr, pixels[0].addr, pixels.len * 4)
|
||||
|
||||
proc encodePng*(width, height, channels: int, data: pointer, len: int): string =
|
||||
proc encodePng*(
|
||||
width, height, channels: int, data: pointer, len: int
|
||||
): string {.raises: [PixieError].} =
|
||||
## Encodes the image data into the PNG file format.
|
||||
## If data points to RGBA data, it is assumed to be straight alpha.
|
||||
|
||||
|
@ -503,7 +507,7 @@ proc encodePng*(width, height, channels: int, data: pointer, len: int): string =
|
|||
result.add("IEND")
|
||||
result.addUint32(crc32(result[result.len - 4 ..< result.len]).swap())
|
||||
|
||||
proc encodePng*(image: Image): string =
|
||||
proc encodePng*(image: Image): string {.raises: [PixieError].} =
|
||||
## Encodes the image data into the PNG file format.
|
||||
if image.data.len == 0:
|
||||
raise newException(
|
||||
|
@ -514,7 +518,7 @@ proc encodePng*(image: Image): string =
|
|||
copy.toStraightAlpha()
|
||||
encodePng(image.width, image.height, 4, copy[0].addr, copy.len * 4)
|
||||
|
||||
proc encodePng*(mask: Mask): string =
|
||||
proc encodePng*(mask: Mask): string {.raises: [PixieError].} =
|
||||
## Encodes the mask data into the PNG file format.
|
||||
if mask.data.len == 0:
|
||||
raise newException(
|
||||
|
|
|
@ -33,15 +33,19 @@ type
|
|||
template failInvalid() =
|
||||
raise newException(PixieError, "Invalid SVG data")
|
||||
|
||||
proc attrOrDefault(node: XmlNode, name, default: string): string =
|
||||
proc attrOrDefault(node: XmlNode, name, default: string): string {.raises: [].} =
|
||||
result = node.attr(name)
|
||||
if result.len == 0:
|
||||
result = default
|
||||
|
||||
proc initCtx(): Ctx =
|
||||
proc initCtx(): Ctx {.raises: [PixieError].} =
|
||||
result.display = true
|
||||
result.fill = parseHtmlColor("black").rgbx
|
||||
result.stroke = parseHtmlColor("black").rgbx
|
||||
try:
|
||||
result.fill = parseHtmlColor("black").rgbx
|
||||
result.stroke = parseHtmlColor("black").rgbx
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
result.strokeWidth = 1
|
||||
result.transform = mat3()
|
||||
result.strokeMiterLimit = defaultMiterLimit
|
||||
|
@ -49,7 +53,7 @@ proc initCtx(): Ctx =
|
|||
result.strokeOpacity = 1
|
||||
result.linearGradients = newTable[string, LinearGradient]()
|
||||
|
||||
proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||
proc decodeCtxInternal(inherited: Ctx, node: XmlNode): Ctx =
|
||||
result = inherited
|
||||
|
||||
proc splitArgs(s: string): seq[string] =
|
||||
|
@ -313,14 +317,23 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
else:
|
||||
failInvalidTransform(transform)
|
||||
|
||||
proc fill(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
||||
proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx {.raises: [PixieError].} =
|
||||
try:
|
||||
decodeCtxInternal(inherited, node)
|
||||
except PixieError as e:
|
||||
raise e
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc fill(img: Image, ctx: Ctx, path: Path) {.inline, raises: [PixieError].} =
|
||||
if ctx.display and ctx.opacity > 0:
|
||||
let paint = newPaint(ctx.fill)
|
||||
if ctx.opacity != 1:
|
||||
paint.opacity = paint.opacity * ctx.opacity
|
||||
img.fillPath(path, paint, ctx.transform, ctx.fillRule)
|
||||
|
||||
proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
||||
proc stroke(img: Image, ctx: Ctx, path: Path) {.inline, raises: [PixieError].} =
|
||||
if ctx.display and ctx.opacity > 0:
|
||||
let paint = newPaint(ctx.stroke)
|
||||
if ctx.opacity != 1:
|
||||
|
@ -336,7 +349,7 @@ proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
|||
dashes = ctx.strokeDashArray
|
||||
)
|
||||
|
||||
proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||
proc drawInternal(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||
if node.kind != xnElement:
|
||||
# Skip <!-- comments -->
|
||||
return
|
||||
|
@ -353,7 +366,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
let ctx = decodeCtx(ctxStack[^1], node)
|
||||
ctxStack.add(ctx)
|
||||
for child in node:
|
||||
img.draw(child, ctxStack)
|
||||
img.drawInternal(child, ctxStack)
|
||||
discard ctxStack.pop()
|
||||
|
||||
of "path":
|
||||
|
@ -543,7 +556,20 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
else:
|
||||
raise newException(PixieError, "Unsupported SVG tag: " & node.tag)
|
||||
|
||||
proc decodeSvg*(data: string, width = 0, height = 0): Image =
|
||||
proc draw(
|
||||
img: Image, node: XmlNode, ctxStack: var seq[Ctx]
|
||||
) {.raises: [PixieError].} =
|
||||
try:
|
||||
drawInternal(img, node, ctxStack)
|
||||
except PixieError as e:
|
||||
raise e
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc decodeSvg*(
|
||||
data: string, width = 0, height = 0
|
||||
): Image {.raises: [PixieError].} =
|
||||
## Render SVG file and return the image. Defaults to the SVG's view box size.
|
||||
try:
|
||||
let root = parseXml(data)
|
||||
|
|
|
@ -334,32 +334,34 @@ type
|
|||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc eofCheck(buf: string, readTo: int) {.inline.} =
|
||||
template eofCheck(buf: string, readTo: int) =
|
||||
if readTo > buf.len:
|
||||
raise newException(PixieError, "Unexpected error reading font data, EOF")
|
||||
|
||||
proc failUnsupported() =
|
||||
template failUnsupported() =
|
||||
raise newException(PixieError, "Unsupported font data")
|
||||
|
||||
proc readUint16Seq(buf: string, offset, len: int): seq[uint16] =
|
||||
proc readUint16Seq(buf: string, offset, len: int): seq[uint16] {.raises: [].} =
|
||||
result = newSeq[uint16](len)
|
||||
for i in 0 ..< len:
|
||||
result[i] = buf.readUint16(offset + i * 2).swap()
|
||||
|
||||
proc readFixed32(buf: string, offset: int): float32 =
|
||||
proc readFixed32(buf: string, offset: int): float32 {.raises: [].} =
|
||||
## Packed 32-bit value with major and minor version numbers.
|
||||
ceil(buf.readInt32(offset).swap().float32 / 65536.0 * 100000.0) / 100000.0
|
||||
|
||||
proc readFixed16(buf: string, offset: int): float32 =
|
||||
proc readFixed16(buf: string, offset: int): float32 {.raises: [].} =
|
||||
## Reads 16-bit signed fixed number with the low 14 bits of fraction (2.14).
|
||||
buf.readInt16(offset).swap().float32 / 16384.0
|
||||
|
||||
proc readLongDateTime(buf: string, offset: int): float64 =
|
||||
proc readLongDateTime(buf: string, offset: int): float64 {.raises: [].} =
|
||||
## Date and time represented in number of seconds since 12:00 midnight,
|
||||
## January 1, 1904, UTC.
|
||||
buf.readInt64(offset).swap().float64 - 2082844800
|
||||
|
||||
proc parseCmapTable(buf: string, offset: int): CmapTable =
|
||||
proc parseCmapTable(
|
||||
buf: string, offset: int
|
||||
): CmapTable {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
buf.eofCheck(i + 4)
|
||||
|
||||
|
@ -454,7 +456,9 @@ proc parseCmapTable(buf: string, offset: int): CmapTable =
|
|||
# TODO implement other cmap platformIDs
|
||||
discard
|
||||
|
||||
proc parseHeadTable(buf: string, offset: int): HeadTable =
|
||||
proc parseHeadTable(
|
||||
buf: string, offset: int
|
||||
): HeadTable {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + 54)
|
||||
|
||||
result = HeadTable()
|
||||
|
@ -483,7 +487,9 @@ proc parseHeadTable(buf: string, offset: int): HeadTable =
|
|||
if result.glyphDataFormat != 0:
|
||||
failUnsupported()
|
||||
|
||||
proc parseHheaTable(buf: string, offset: int): HheaTable =
|
||||
proc parseHheaTable(
|
||||
buf: string, offset: int
|
||||
): HheaTable {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + 36)
|
||||
|
||||
result = HheaTable()
|
||||
|
@ -512,7 +518,9 @@ proc parseHheaTable(buf: string, offset: int): HheaTable =
|
|||
failUnsupported()
|
||||
result.numberOfHMetrics = buf.readUint16(offset + 34).swap()
|
||||
|
||||
proc parseMaxpTable(buf: string, offset: int): MaxpTable =
|
||||
proc parseMaxpTable(
|
||||
buf: string, offset: int
|
||||
): MaxpTable {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + 32)
|
||||
|
||||
result = MaxpTable()
|
||||
|
@ -536,7 +544,7 @@ proc parseMaxpTable(buf: string, offset: int): MaxpTable =
|
|||
|
||||
proc parseHmtxTable(
|
||||
buf: string, offset: int, hhea: HheaTable, maxp: MaxpTable
|
||||
): HmtxTable =
|
||||
): HmtxTable {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
let
|
||||
|
@ -557,7 +565,9 @@ proc parseHmtxTable(
|
|||
result.leftSideBearings.add(buf.readInt16(i).swap())
|
||||
i += 2
|
||||
|
||||
proc parseNameTable(buf: string, offset: int): NameTable =
|
||||
proc parseNameTable(
|
||||
buf: string, offset: int
|
||||
): NameTable {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 6)
|
||||
|
@ -583,7 +593,9 @@ proc parseNameTable(buf: string, offset: int): NameTable =
|
|||
record.offset = buf.readUint16(i + 10).swap()
|
||||
i += 12
|
||||
|
||||
proc parseOS2Table(buf: string, offset: int): OS2Table =
|
||||
proc parseOS2Table(
|
||||
buf: string, offset: int
|
||||
): OS2Table {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 78)
|
||||
|
@ -647,7 +659,7 @@ proc parseOS2Table(buf: string, offset: int): OS2Table =
|
|||
|
||||
proc parseLocaTable(
|
||||
buf: string, offset: int, head: HeadTable, maxp: MaxpTable
|
||||
): LocaTable =
|
||||
): LocaTable {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
result = LocaTable()
|
||||
|
@ -664,13 +676,17 @@ proc parseLocaTable(
|
|||
result.offsets.add(buf.readUint32(i).swap())
|
||||
i += 4
|
||||
|
||||
proc parseGlyfTable(buf: string, offset: int, loca: LocaTable): GlyfTable =
|
||||
proc parseGlyfTable(
|
||||
buf: string, offset: int, loca: LocaTable
|
||||
): GlyfTable {.raises: [].} =
|
||||
result = GlyfTable()
|
||||
result.offsets.setLen(loca.offsets.len)
|
||||
for glyphId in 0 ..< loca.offsets.len:
|
||||
result.offsets[glyphId] = offset.uint32 + loca.offsets[glyphId]
|
||||
|
||||
proc parseKernTable(buf: string, offset: int): KernTable =
|
||||
proc parseKernTable(
|
||||
buf: string, offset: int
|
||||
): KernTable {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
@ -721,11 +737,10 @@ proc parseKernTable(buf: string, offset: int): KernTable =
|
|||
if pair.value != 0:
|
||||
let key = (pair.left, pair.right)
|
||||
var value = pair.value.float32
|
||||
if key in result.kerningPairs:
|
||||
if (table.coverage and 0b1000) != 0: # Override
|
||||
discard
|
||||
else: # Accumulate
|
||||
value += result.kerningPairs[key]
|
||||
if (table.coverage and 0b1000) != 0: # Override
|
||||
discard
|
||||
else: # Accumulate
|
||||
value += result.kerningPairs.getOrDefault(key, 0)
|
||||
result.kerningPairs[key] = value
|
||||
|
||||
elif version == 1:
|
||||
|
@ -733,7 +748,9 @@ proc parseKernTable(buf: string, offset: int): KernTable =
|
|||
else:
|
||||
failUnsupported()
|
||||
|
||||
# proc parseLangSys(buf: string, offset: int): LangSys =
|
||||
# proc parseLangSys(
|
||||
# buf: string, offset: int
|
||||
# ): LangSys {.raises: [PixieError].} =
|
||||
# var i = offset
|
||||
|
||||
# buf.eofCheck(i + 6)
|
||||
|
@ -821,14 +838,16 @@ proc parseKernTable(buf: string, offset: int): KernTable =
|
|||
# result.featureRecords.add(featureRecord)
|
||||
# i += 6
|
||||
|
||||
proc parseRangeRecord(buf: string, offset: int): RangeRecord =
|
||||
proc parseRangeRecord(
|
||||
buf: string, offset: int
|
||||
): RangeRecord {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + 6)
|
||||
|
||||
result.startGlyphID = buf.readUint16(offset + 0).swap()
|
||||
result.endGlyphID = buf.readUint16(offset + 2).swap()
|
||||
result.startCoverageIndex = buf.readUint16(offset + 4).swap()
|
||||
|
||||
proc parseCoverage(buf: string, offset: int): Coverage =
|
||||
proc parseCoverage(buf: string, offset: int): Coverage {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 4)
|
||||
|
@ -866,7 +885,7 @@ proc parseCoverage(buf: string, offset: int): Coverage =
|
|||
else:
|
||||
failUnsupported()
|
||||
|
||||
proc valueFormatSize(valueFormat: uint16): int =
|
||||
proc valueFormatSize(valueFormat: uint16): int {.raises: [].} =
|
||||
# countSetBits(valueFormat) * 2
|
||||
var
|
||||
n = valueFormat
|
||||
|
@ -878,7 +897,7 @@ proc valueFormatSize(valueFormat: uint16): int =
|
|||
|
||||
proc parseValueRecord(
|
||||
buf: string, offset: int, valueFormat: uint16
|
||||
): ValueRecord =
|
||||
): ValueRecord {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + valueFormatSize(valueFormat))
|
||||
|
||||
var i = offset
|
||||
|
@ -909,7 +928,7 @@ proc parseValueRecord(
|
|||
|
||||
proc parsePairValueRecord(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||
): PairValueRecord =
|
||||
): PairValueRecord {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
@ -923,7 +942,7 @@ proc parsePairValueRecord(
|
|||
|
||||
proc parsePairSet(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||
): PairSet =
|
||||
): PairSet {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
@ -942,7 +961,7 @@ proc parsePairSet(
|
|||
|
||||
proc parseClass2Record(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||
): Class2Record =
|
||||
): Class2Record {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(
|
||||
|
@ -955,7 +974,7 @@ proc parseClass2Record(
|
|||
|
||||
proc parseClass1Record(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16
|
||||
): Class1Record =
|
||||
): Class1Record {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
result.class2Records.setLen(class2Count.int)
|
||||
|
@ -964,14 +983,16 @@ proc parseClass1Record(
|
|||
parseClass2Record(buf, i, valueFormat1, valueFormat2)
|
||||
i += valueFormatSize(valueFormat1) + valueFormatSize(valueFormat2)
|
||||
|
||||
proc parseClassRangeRecord(buf: string, offset: int): ClassRangeRecord =
|
||||
proc parseClassRangeRecord(
|
||||
buf: string, offset: int
|
||||
): ClassRangeRecord {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + 6)
|
||||
|
||||
result.startGlyphID = buf.readUint16(offset + 0).swap()
|
||||
result.endGlyphID = buf.readUint16(offset + 2).swap()
|
||||
result.class = buf.readUint16(offset + 4).swap()
|
||||
|
||||
proc parseClassDef(buf: string, offset: int): ClassDef =
|
||||
proc parseClassDef(buf: string, offset: int): ClassDef {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
@ -1003,7 +1024,7 @@ proc parseClassDef(buf: string, offset: int): ClassDef =
|
|||
else:
|
||||
failUnsupported()
|
||||
|
||||
proc parsePairPos(buf: string, offset: int): PairPos =
|
||||
proc parsePairPos(buf: string, offset: int): PairPos {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 4)
|
||||
|
@ -1136,7 +1157,9 @@ proc parsePairPos(buf: string, offset: int): PairPos =
|
|||
else:
|
||||
failUnsupported()
|
||||
|
||||
proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup =
|
||||
proc parseLookup(
|
||||
buf: string, offset: int, gpos: GposTable
|
||||
): Lookup {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 6)
|
||||
|
@ -1162,7 +1185,9 @@ proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup =
|
|||
pairPos.classPairAdjustments.len > 0:
|
||||
gpos.lookupList.pairPosTables.add(pairPos)
|
||||
|
||||
proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList =
|
||||
proc parseLookupList(
|
||||
buf: string, offset: int, gpos: GposTable
|
||||
): LookupList {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
@ -1177,7 +1202,9 @@ proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList =
|
|||
for lookupOffset in result.lookupoffsets:
|
||||
result.lookups.add(parseLookup(buf, offset + lookupOffset.int, gpos))
|
||||
|
||||
proc parseGposTable(buf: string, offset: int): GPOSTable =
|
||||
proc parseGposTable(
|
||||
buf: string, offset: int
|
||||
): GPOSTable {.raises: [PixieError].} =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 10)
|
||||
|
@ -1208,7 +1235,9 @@ proc parseGposTable(buf: string, offset: int): GPOSTable =
|
|||
result.lookupList =
|
||||
parseLookupList(buf, offset + result.lookupListOffset.int, result)
|
||||
|
||||
proc parsePostTable(buf: string, offset: int): PostTable =
|
||||
proc parsePostTable(
|
||||
buf: string, offset: int
|
||||
): PostTable {.raises: [PixieError].} =
|
||||
buf.eofCheck(offset + 14)
|
||||
|
||||
result = PostTable()
|
||||
|
@ -1218,15 +1247,14 @@ proc parsePostTable(buf: string, offset: int): PostTable =
|
|||
result.underlineThickness = buf.readInt16(offset + 10).swap()
|
||||
result.isFixedPitch = buf.readUint32(offset + 12).swap()
|
||||
|
||||
proc getGlyphId(opentype: OpenType, rune: Rune): uint16 {.inline.} =
|
||||
if rune in opentype.cmap.runeToGlyphId:
|
||||
result = opentype.cmap.runeToGlyphId[rune]
|
||||
else:
|
||||
discard # Index 0 is the "missing character" glyph
|
||||
proc getGlyphId(opentype: OpenType, rune: Rune): uint16 {.inline, raises: [].} =
|
||||
result = opentype.cmap.runeToGlyphId.getOrDefault(rune, 0)
|
||||
|
||||
proc parseGlyph(opentype: OpenType, glyphId: uint16): Path
|
||||
proc parseGlyph(opentype: OpenType, glyphId: uint16): Path {.raises: [PixieError].}
|
||||
|
||||
proc parseGlyphPath(buf: string, offset, numberOfContours: int): Path =
|
||||
proc parseGlyphPath(
|
||||
buf: string, offset, numberOfContours: int
|
||||
): Path {.raises: [PixieError].} =
|
||||
if numberOfContours < 0:
|
||||
raise newException(PixieError, "Glyph numberOfContours must be >= 0")
|
||||
|
||||
|
@ -1359,7 +1387,9 @@ proc parseGlyphPath(buf: string, offset, numberOfContours: int): Path =
|
|||
|
||||
result.closePath()
|
||||
|
||||
proc parseCompositeGlyph(opentype: OpenType, offset: int): Path =
|
||||
proc parseCompositeGlyph(
|
||||
opentype: OpenType, offset: int
|
||||
): Path {.raises: [PixieError].} =
|
||||
result = newPath()
|
||||
|
||||
var
|
||||
|
@ -1454,7 +1484,9 @@ proc parseCompositeGlyph(opentype: OpenType, offset: int): Path =
|
|||
|
||||
moreComponents = (flags and 0b100000) != 0
|
||||
|
||||
proc parseGlyph(opentype: OpenType, glyphId: uint16): Path =
|
||||
proc parseGlyph(
|
||||
opentype: OpenType, glyphId: uint16
|
||||
): Path {.raises: [PixieError].} =
|
||||
if glyphId.int >= opentype.glyf.offsets.len:
|
||||
raise newException(PixieError, "Invalid glyph ID " & $glyphId)
|
||||
|
||||
|
@ -1482,16 +1514,21 @@ proc parseGlyph(opentype: OpenType, glyphId: uint16): Path =
|
|||
else:
|
||||
parseGlyphPath(opentype.buf, i, numberOfContours)
|
||||
|
||||
proc parseGlyph(opentype: OpenType, rune: Rune): Path {.inline.} =
|
||||
proc parseGlyph(
|
||||
opentype: OpenType, rune: Rune
|
||||
): Path {.inline, raises: [PixieError].} =
|
||||
opentype.parseGlyph(opentype.getGlyphId(rune))
|
||||
|
||||
proc getGlyphPath*(opentype: OpenType, rune: Rune): Path =
|
||||
proc getGlyphPath*(
|
||||
opentype: OpenType, rune: Rune
|
||||
): Path {.raises: [PixieError].} =
|
||||
if rune notin opentype.glyphPaths:
|
||||
opentype.glyphPaths[rune] = opentype.parseGlyph(rune)
|
||||
opentype.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
||||
opentype.glyphPaths[rune]
|
||||
let path = opentype.parseGlyph(rune)
|
||||
path.transform(scale(vec2(1, -1)))
|
||||
opentype.glyphPaths[rune] = path
|
||||
opentype.glyphPaths.getOrDefault(rune, nil) # Never actually returns nil
|
||||
|
||||
proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 =
|
||||
proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 {.raises: [].} =
|
||||
let glyphId = opentype.getGlyphId(rune).int
|
||||
if glyphId < opentype.hmtx.hMetrics.len:
|
||||
result = opentype.hmtx.hMetrics[glyphId].leftSideBearing.float32
|
||||
|
@ -1500,21 +1537,23 @@ proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 =
|
|||
if index > 0 and index < opentype.hmtx.leftSideBearings.len:
|
||||
result = opentype.hmtx.leftSideBearings[index].float32
|
||||
|
||||
proc getAdvance*(opentype: OpenType, rune: Rune): float32 =
|
||||
proc getAdvance*(opentype: OpenType, rune: Rune): float32 {.raises: [].} =
|
||||
let glyphId = opentype.getGlyphId(rune).int
|
||||
if glyphId < opentype.hmtx.hMetrics.len:
|
||||
result = opentype.hmtx.hMetrics[glyphId].advanceWidth.float32
|
||||
else:
|
||||
result = opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
||||
|
||||
proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 =
|
||||
proc getKerningAdjustment*(
|
||||
opentype: OpenType, left, right: Rune
|
||||
): float32 {.raises: [].} =
|
||||
if left notin opentype.cmap.runeToGlyphId or
|
||||
right notin opentype.cmap.runeToGlyphId:
|
||||
return
|
||||
|
||||
let
|
||||
leftGlyphId = opentype.cmap.runeToGlyphId[left]
|
||||
rightGlyphId = opentype.cmap.runeToGlyphId[right]
|
||||
leftGlyphId = opentype.cmap.runeToGlyphId.getOrDefault(left, 0)
|
||||
rightGlyphId = opentype.cmap.runeToGlyphId.getOrDefault(right, 0)
|
||||
glyphPair = (leftGlyphId, rightGlyphId)
|
||||
|
||||
if opentype.gpos != nil:
|
||||
|
@ -1525,27 +1564,23 @@ proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 =
|
|||
case pairPos.posFormat:
|
||||
of 1:
|
||||
if glyphPair in pairPos.glyphPairAdjustments:
|
||||
result = pairPos.glyphPairAdjustments[glyphPair].float32
|
||||
result = pairPos.glyphPairAdjustments.getOrDefault(glyphPair, 0).float32
|
||||
break
|
||||
of 2:
|
||||
var leftClass, rightClass: uint16
|
||||
if leftGlyphId in pairPos.glyphIdToClass1:
|
||||
leftClass = pairPos.glyphIdToClass1[leftGlyphId]
|
||||
if rightGlyphId in pairPos.glyphIdToClass2:
|
||||
rightClass = pairPos.glyphIdToClass2[rightGlyphId]
|
||||
|
||||
let classPair = (leftClass, rightClass)
|
||||
let
|
||||
leftClass = pairPos.glyphIdToClass1.getOrDefault(leftGlyphId, 0)
|
||||
rightClass = pairPos.glyphIdToClass2.getOrDefault(rightGlyphId, 0)
|
||||
classPair = (leftClass, rightClass)
|
||||
if classPair in pairPos.classPairAdjustments:
|
||||
result = pairPos.classPairAdjustments[classPair].float32
|
||||
result = pairPos.classPairAdjustments.getOrDefault(classPair, 0).float32
|
||||
break
|
||||
else:
|
||||
discard
|
||||
|
||||
elif opentype.kern != nil:
|
||||
if glyphPair in opentype.kern.kerningPairs:
|
||||
result = opentype.kern.kerningPairs[glyphPair]
|
||||
result = opentype.kern.kerningPairs.getOrDefault(glyphPair, 0)
|
||||
|
||||
proc parseOpenType*(buf: string): OpenType =
|
||||
proc parseOpenType*(buf: string): OpenType {.raises: [PixieError].} =
|
||||
result = OpenType()
|
||||
result.buf = buf
|
||||
|
||||
|
@ -1572,36 +1607,31 @@ proc parseOpenType*(buf: string): OpenType =
|
|||
result.tableRecords[tableRecord.tag] = tableRecord
|
||||
i += 16
|
||||
|
||||
const requiredTables = [
|
||||
"cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "loca", "glyf",
|
||||
"post"
|
||||
]
|
||||
for table in requiredTables:
|
||||
if table notin result.tableRecords:
|
||||
raise newException(PixieError, "Missing required font table " & table)
|
||||
try:
|
||||
result.cmap = parseCmapTable(buf, result.tableRecords["cmap"].offset.int)
|
||||
result.head = parseHeadTable(buf, result.tableRecords["head"].offset.int)
|
||||
result.hhea = parseHheaTable(buf, result.tableRecords["hhea"].offset.int)
|
||||
result.maxp = parseMaxpTable(buf, result.tableRecords["maxp"].offset.int)
|
||||
result.hmtx = parseHmtxTable(
|
||||
buf, result.tableRecords["hmtx"].offset.int, result.hhea, result.maxp
|
||||
)
|
||||
result.name = parseNameTable(buf, result.tableRecords["name"].offset.int)
|
||||
result.os2 = parseOS2Table(buf, result.tableRecords["OS/2"].offset.int)
|
||||
result.loca = parseLocaTable(
|
||||
buf, result.tableRecords["loca"].offset.int, result.head, result.maxp
|
||||
)
|
||||
result.glyf =
|
||||
parseGlyfTable(buf, result.tableRecords["glyf"].offset.int, result.loca)
|
||||
|
||||
result.cmap = parseCmapTable(buf, result.tableRecords["cmap"].offset.int)
|
||||
result.head = parseHeadTable(buf, result.tableRecords["head"].offset.int)
|
||||
result.hhea = parseHheaTable(buf, result.tableRecords["hhea"].offset.int)
|
||||
result.maxp = parseMaxpTable(buf, result.tableRecords["maxp"].offset.int)
|
||||
result.hmtx = parseHmtxTable(
|
||||
buf, result.tableRecords["hmtx"].offset.int, result.hhea, result.maxp
|
||||
)
|
||||
result.name = parseNameTable(buf, result.tableRecords["name"].offset.int)
|
||||
result.os2 = parseOS2Table(buf, result.tableRecords["OS/2"].offset.int)
|
||||
result.loca = parseLocaTable(
|
||||
buf, result.tableRecords["loca"].offset.int, result.head, result.maxp
|
||||
)
|
||||
result.glyf =
|
||||
parseGlyfTable(buf, result.tableRecords["glyf"].offset.int, result.loca)
|
||||
if "kern" in result.tableRecords:
|
||||
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
||||
|
||||
if "kern" in result.tableRecords:
|
||||
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
||||
if "GPOS" in result.tableRecords:
|
||||
result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int)
|
||||
|
||||
if "GPOS" in result.tableRecords:
|
||||
result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int)
|
||||
|
||||
result.post = parsePostTable(buf, result.tableRecords["post"].offset.int)
|
||||
result.post = parsePostTable(buf, result.tableRecords["post"].offset.int)
|
||||
except KeyError as e:
|
||||
raise newException(PixieError, "Missing required font table: " & e.msg)
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -8,27 +8,22 @@ type SvgFont* = ref object
|
|||
missingGlyphAdvance: float32
|
||||
missingGlyphPath: Path
|
||||
|
||||
proc getGlyphPath*(svgFont: SvgFont, rune: Rune): Path =
|
||||
if rune in svgFont.glyphPaths:
|
||||
svgFont.glyphPaths[rune]
|
||||
else:
|
||||
svgFont.missingGlyphPath
|
||||
proc getGlyphPath*(svgFont: SvgFont, rune: Rune): Path {.raises: [].} =
|
||||
svgFont.glyphPaths.getOrDefault(rune, svgFont.missingGlyphPath)
|
||||
|
||||
proc getAdvance*(svgFont: SvgFont, rune: Rune): float32 =
|
||||
if rune in svgFont.advances:
|
||||
svgFont.advances[rune]
|
||||
else:
|
||||
svgFont.missingGlyphAdvance
|
||||
proc getAdvance*(svgFont: SvgFont, rune: Rune): float32 {.raises: [].} =
|
||||
svgFont.advances.getOrDefault(rune, svgFont.missingGlyphAdvance)
|
||||
|
||||
proc getKerningAdjustment*(svgFont: SvgFont, left, right: Rune): float32 =
|
||||
proc getKerningAdjustment*(
|
||||
svgFont: SvgFont, left, right: Rune
|
||||
): float32 {.raises: [].} =
|
||||
let pair = (left, right)
|
||||
if pair in svgFont.kerningPairs:
|
||||
result = svgFont.kerningPairs[pair]
|
||||
result = svgFont.kerningPairs.getOrDefault(pair, 0)
|
||||
|
||||
proc failInvalid() =
|
||||
template failInvalid() =
|
||||
raise newException(PixieError, "Invalid SVG font data")
|
||||
|
||||
proc parseFloat(node: XmlNode, attr: string): float32 =
|
||||
proc parseFloat(node: XmlNode, attr: string): float32 {.raises: [PixieError].} =
|
||||
let value = node.attr(attr)
|
||||
if value.len == 0:
|
||||
raise newException(PixieError, "SVG font missing attr " & attr)
|
||||
|
@ -37,12 +32,17 @@ proc parseFloat(node: XmlNode, attr: string): float32 =
|
|||
except:
|
||||
failInvalid()
|
||||
|
||||
proc parseSvgFont*(buf: string): SvgFont =
|
||||
proc parseSvgFont*(buf: string): SvgFont {.raises: [PixieError].} =
|
||||
result = SvgFont()
|
||||
|
||||
let
|
||||
root = parseXml(buf)
|
||||
defs = root.child("defs")
|
||||
let root =
|
||||
try:
|
||||
parseXml(buf)
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
let defs = root.child("defs")
|
||||
if defs == nil:
|
||||
failInvalid()
|
||||
|
||||
|
@ -75,8 +75,9 @@ proc parseSvgFont*(buf: string): SvgFont =
|
|||
if node.attr("horiz-adv-x").len > 0:
|
||||
advance = node.parseFloat("horiz-adv-x")
|
||||
result.advances[rune] = advance
|
||||
result.glyphPaths[rune] = parsePath(node.attr("d"))
|
||||
result.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
||||
let path = parsePath(node.attr("d"))
|
||||
path.transform(scale(vec2(1, -1)))
|
||||
result.glyphPaths[rune] = path
|
||||
else:
|
||||
discard # Multi-rune unicode?
|
||||
of "hkern":
|
||||
|
|
|
@ -53,46 +53,48 @@ type
|
|||
# tcSmallCaps
|
||||
# tcSmallCapsForced
|
||||
|
||||
proc ascent*(typeface: Typeface): float32 {.inline.} =
|
||||
proc ascent*(typeface: Typeface): float32 {.raises: [].} =
|
||||
## The font ascender value in font units.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.hhea.ascender.float32
|
||||
else:
|
||||
typeface.svgFont.ascent
|
||||
|
||||
proc descent*(typeface: Typeface): float32 {.inline.} =
|
||||
proc descent*(typeface: Typeface): float32 {.raises: [].} =
|
||||
## The font descender value in font units.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.hhea.descender.float32
|
||||
else:
|
||||
typeface.svgFont.descent
|
||||
|
||||
proc lineGap*(typeface: Typeface): float32 {.inline.} =
|
||||
proc lineGap*(typeface: Typeface): float32 {.raises: [].} =
|
||||
## The font line gap value in font units.
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.hhea.lineGap.float32
|
||||
|
||||
proc lineHeight*(typeface: Typeface): float32 {.inline.} =
|
||||
proc lineHeight*(typeface: Typeface): float32 {.inline, raises: [].} =
|
||||
## The default line height in font units.
|
||||
typeface.ascent - typeface.descent + typeface.lineGap
|
||||
|
||||
proc underlinePosition(typeface: Typeface): float32 {.inline.} =
|
||||
proc underlinePosition(typeface: Typeface): float32 {.raises: [].} =
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.post.underlinePosition.float32
|
||||
|
||||
proc underlineThickness(typeface: Typeface): float32 {.inline.} =
|
||||
proc underlineThickness(typeface: Typeface): float32 {.raises: [].} =
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.post.underlineThickness.float32
|
||||
|
||||
proc strikeoutPosition(typeface: Typeface): float32 {.inline.} =
|
||||
proc strikeoutPosition(typeface: Typeface): float32 {.raises: [].} =
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.os2.yStrikeoutPosition.float32
|
||||
|
||||
proc strikeoutThickness(typeface: Typeface): float32 {.inline.} =
|
||||
proc strikeoutThickness(typeface: Typeface): float32 {.raises: [].} =
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.os2.yStrikeoutSize.float32
|
||||
|
||||
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
|
||||
proc getGlyphPath*(
|
||||
typeface: Typeface, rune: Rune
|
||||
): Path {.inline, raises: [PixieError].} =
|
||||
## The glyph path for the rune.
|
||||
result = newPath()
|
||||
if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu)
|
||||
|
@ -101,7 +103,7 @@ proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
|
|||
else:
|
||||
result.addPath(typeface.svgFont.getGlyphPath(rune))
|
||||
|
||||
proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
||||
proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline, raises: [].} =
|
||||
## The advance for the rune in pixels.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.getAdvance(rune)
|
||||
|
@ -110,46 +112,47 @@ proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
|||
|
||||
proc getKerningAdjustment*(
|
||||
typeface: Typeface, left, right: Rune
|
||||
): float32 {.inline.} =
|
||||
): float32 {.inline, raises: [].} =
|
||||
## The kerning adjustment for the rune pair, in pixels.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.getKerningAdjustment(left, right)
|
||||
else:
|
||||
typeface.svgfont.getKerningAdjustment(left, right)
|
||||
|
||||
proc scale*(font: Font): float32 {.inline.} =
|
||||
proc scale*(font: Font): float32 {.inline, raises: [].} =
|
||||
## The scale factor to transform font units into pixels.
|
||||
if font.typeface.opentype != nil:
|
||||
font.size / font.typeface.opentype.head.unitsPerEm.float32
|
||||
else:
|
||||
font.size / font.typeface.svgFont.unitsPerEm
|
||||
|
||||
proc defaultLineHeight*(font: Font): float32 {.inline.} =
|
||||
proc defaultLineHeight*(font: Font): float32 {.inline, raises: [].} =
|
||||
## The default line height in pixels for the current font size.
|
||||
let fontUnits =
|
||||
font.typeface.ascent - font.typeface.descent + font.typeface.lineGap
|
||||
round(fontUnits * font.scale)
|
||||
|
||||
proc paint*(font: Font): var Paint =
|
||||
proc paint*(font: Font): var Paint {.inline, raises: [].} =
|
||||
font.paints[0]
|
||||
|
||||
proc `paint=`*(font: Font, paint: Paint) =
|
||||
proc `paint=`*(font: Font, paint: Paint) {.inline, raises: [].} =
|
||||
font.paints = @[paint]
|
||||
|
||||
proc newFont*(typeface: Typeface): Font =
|
||||
proc newFont*(typeface: Typeface): Font {.raises: [].} =
|
||||
result = Font()
|
||||
result.typeface = typeface
|
||||
result.size = 12
|
||||
result.lineHeight = AutoLineHeight
|
||||
result.paint = rgbx(0, 0, 0, 255)
|
||||
result.paint = newPaint(pkSolid)
|
||||
result.paint.color = color(0, 0, 0, 1)
|
||||
|
||||
proc newSpan*(text: string, font: Font): Span =
|
||||
proc newSpan*(text: string, font: Font): Span {.raises: [].} =
|
||||
## Creates a span, associating a font with the text.
|
||||
result = Span()
|
||||
result.text = text
|
||||
result.font = font
|
||||
|
||||
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
||||
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) {.raises: [].} =
|
||||
case textCase:
|
||||
of tcNormal:
|
||||
discard
|
||||
|
@ -166,7 +169,7 @@ proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
|||
rune = rune.toUpper()
|
||||
prevRune = rune
|
||||
|
||||
proc canWrap(rune: Rune): bool {.inline.} =
|
||||
proc canWrap(rune: Rune): bool {.inline, raises: [].} =
|
||||
rune == Rune(32) or rune.isWhiteSpace()
|
||||
|
||||
proc typeset*(
|
||||
|
@ -175,7 +178,7 @@ proc typeset*(
|
|||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
wrap = true
|
||||
): Arrangement =
|
||||
): Arrangement {.raises: [].} =
|
||||
## Lays out the character glyphs and returns the arrangement.
|
||||
## Optional parameters:
|
||||
## bounds: width determines wrapping and hAlign, height for vAlign
|
||||
|
@ -396,7 +399,7 @@ proc typeset*(
|
|||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
wrap = true
|
||||
): Arrangement {.inline.} =
|
||||
): Arrangement {.inline, raises: [].} =
|
||||
## Lays out the character glyphs and returns the arrangement.
|
||||
## Optional parameters:
|
||||
## bounds: width determines wrapping and hAlign, height for vAlign
|
||||
|
@ -405,7 +408,7 @@ proc typeset*(
|
|||
## wrap: enable/disable text wrapping
|
||||
typeset(@[newSpan(text, font)], bounds, hAlign, vAlign, wrap)
|
||||
|
||||
proc computeBounds*(arrangement: Arrangement): Vec2 =
|
||||
proc computeBounds*(arrangement: Arrangement): Vec2 {.raises: [].} =
|
||||
## Computes the width and height of the arrangement in pixels.
|
||||
if arrangement.runes.len > 0:
|
||||
for i in 0 ..< arrangement.runes.len:
|
||||
|
@ -415,22 +418,22 @@ proc computeBounds*(arrangement: Arrangement): Vec2 =
|
|||
let finalRect = arrangement.selectionRects[^1]
|
||||
result.y = finalRect.y + finalRect.h
|
||||
|
||||
proc computeBounds*(font: Font, text: string): Vec2 {.inline.} =
|
||||
proc computeBounds*(font: Font, text: string): Vec2 {.inline, raises: [].} =
|
||||
## Computes the width and height of the text in pixels.
|
||||
font.typeset(text).computeBounds()
|
||||
|
||||
proc computeBounds*(spans: seq[Span]): Vec2 {.inline.} =
|
||||
proc computeBounds*(spans: seq[Span]): Vec2 {.inline, raises: [].} =
|
||||
## Computes the width and height of the spans in pixels.
|
||||
typeset(spans).computeBounds()
|
||||
|
||||
proc parseOtf*(buf: string): Typeface =
|
||||
proc parseOtf*(buf: string): Typeface {.raises: [PixieError].} =
|
||||
result = Typeface()
|
||||
result.opentype = parseOpenType(buf)
|
||||
|
||||
proc parseTtf*(buf: string): Typeface =
|
||||
proc parseTtf*(buf: string): Typeface {.raises: [PixieError].} =
|
||||
parseOtf(buf)
|
||||
|
||||
proc parseSvgFont*(buf: string): Typeface =
|
||||
proc parseSvgFont*(buf: string): Typeface {.raises: [PixieError].} =
|
||||
result = Typeface()
|
||||
result.svgFont = svgfont.parseSvgFont(buf)
|
||||
|
||||
|
@ -444,7 +447,7 @@ proc textUber(
|
|||
miterLimit = defaultMiterLimit,
|
||||
dashes: seq[float32] = @[],
|
||||
stroke: static[bool] = false
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
var line: int
|
||||
for spanIndex, (start, stop) in arrangement.spans:
|
||||
let
|
||||
|
@ -519,7 +522,7 @@ proc fillText*(
|
|||
target: Image | Mask,
|
||||
arrangement: Arrangement,
|
||||
transform = mat3()
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Fills the text arrangement.
|
||||
textUber(
|
||||
target,
|
||||
|
@ -535,7 +538,7 @@ proc fillText*(
|
|||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Typesets and fills the text. Optional parameters:
|
||||
## transform: translation or matrix to apply
|
||||
## bounds: width determines wrapping and hAlign, height for vAlign
|
||||
|
@ -552,7 +555,7 @@ proc strokeText*(
|
|||
lineJoin = ljMiter,
|
||||
miterLimit = defaultMiterLimit,
|
||||
dashes: seq[float32] = @[]
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Strokes the text arrangement.
|
||||
textUber(
|
||||
target,
|
||||
|
@ -579,7 +582,7 @@ proc strokeText*(
|
|||
lineJoin = ljMiter,
|
||||
miterLimit = defaultMiterLimit,
|
||||
dashes: seq[float32] = @[]
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Typesets and strokes the text. Optional parameters:
|
||||
## transform: translation or matrix to apply
|
||||
## bounds: width determines wrapping and hAlign, height for vAlign
|
||||
|
@ -598,20 +601,24 @@ proc strokeText*(
|
|||
dashes
|
||||
)
|
||||
|
||||
proc readTypeface*(filePath: string): Typeface =
|
||||
proc readTypeface*(filePath: string): Typeface {.raises: [PixieError].} =
|
||||
## Loads a typeface from a file.
|
||||
result =
|
||||
case splitFile(filePath).ext.toLowerAscii():
|
||||
of ".ttf":
|
||||
parseTtf(readFile(filePath))
|
||||
of ".otf":
|
||||
parseOtf(readFile(filePath))
|
||||
of ".svg":
|
||||
parseSvgFont(readFile(filePath))
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported font format")
|
||||
try:
|
||||
result =
|
||||
case splitFile(filePath).ext.toLowerAscii():
|
||||
of ".ttf":
|
||||
parseTtf(readFile(filePath))
|
||||
of ".otf":
|
||||
parseOtf(readFile(filePath))
|
||||
of ".svg":
|
||||
parseSvgFont(readFile(filePath))
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported font format")
|
||||
except IOError as e:
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
result.filePath = filePath
|
||||
|
||||
proc readFont*(filePath: string): Font =
|
||||
proc readFont*(filePath: string): Font {.raises: [PixieError].} =
|
||||
## Loads a font from a file.
|
||||
newFont(readTypeface(filePath))
|
||||
|
|
|
@ -14,7 +14,7 @@ type
|
|||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc newImage*(width, height: int): Image =
|
||||
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")
|
||||
|
@ -24,7 +24,7 @@ proc newImage*(width, height: int): Image =
|
|||
result.height = height
|
||||
result.data = newSeq[ColorRGBX](width * height)
|
||||
|
||||
proc newImage*(mask: Mask): Image =
|
||||
proc newImage*(mask: Mask): Image {.raises: [PixieError].} =
|
||||
result = newImage(mask.width, mask.height)
|
||||
var i: int
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
|
@ -39,59 +39,63 @@ proc newImage*(mask: Mask): Image =
|
|||
let v = mask.data[i]
|
||||
result.data[i] = rgbx(v, v, v, v)
|
||||
|
||||
proc wh*(image: Image): Vec2 {.inline.} =
|
||||
proc wh*(image: Image): Vec2 {.inline, raises: [].} =
|
||||
## Return with and height as a size vector.
|
||||
vec2(image.width.float32, image.height.float32)
|
||||
|
||||
proc copy*(image: Image): Image =
|
||||
proc copy*(image: Image): Image {.raises: [PixieError].} =
|
||||
## Copies the image data into a new image.
|
||||
result = newImage(image.width, image.height)
|
||||
result.data = image.data
|
||||
|
||||
proc `$`*(image: Image): string =
|
||||
proc `$`*(image: Image): string {.raises: [].} =
|
||||
## Prints the image size.
|
||||
"<Image " & $image.width & "x" & $image.height & ">"
|
||||
|
||||
proc inside*(image: Image, x, y: int): bool {.inline.} =
|
||||
proc inside*(image: Image, x, y: int): bool {.inline, raises: [].} =
|
||||
## Returns true if (x, y) is inside the image.
|
||||
x >= 0 and x < image.width and y >= 0 and y < image.height
|
||||
|
||||
proc dataIndex*(image: Image, x, y: int): int {.inline.} =
|
||||
proc dataIndex*(image: Image, x, y: int): int {.inline, raises: [].} =
|
||||
image.width * y + x
|
||||
|
||||
proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBX {.inline.} =
|
||||
proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBX {.inline, raises: [].} =
|
||||
## Gets a color 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 = image.data[image.width * y + x]
|
||||
|
||||
proc `[]`*(image: Image, x, y: int): ColorRGBX {.inline.} =
|
||||
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)
|
||||
|
||||
proc getColor*(image: Image, x, y: int): Color =
|
||||
proc getColor*(image: Image, x, y: int): Color {.inline, raises: [].} =
|
||||
## Gets a color at (x, y) or returns transparent black if outside of bounds.
|
||||
image[x, y].color()
|
||||
|
||||
proc setRgbaUnsafe*(image: Image, x, y: int, color: SomeColor) {.inline.} =
|
||||
proc setRgbaUnsafe*(
|
||||
image: Image, x, y: int, color: SomeColor
|
||||
) {.inline, raises: [].} =
|
||||
## Sets a color from (x, y) coordinates.
|
||||
## * No bounds checking *
|
||||
## Make sure that x, y are in bounds.
|
||||
## Failure in the assumptions will case unsafe memory writes.
|
||||
image.data[image.dataIndex(x, y)] = color.asRgbx()
|
||||
|
||||
proc `[]=`*(image: Image, x, y: int, color: SomeColor) {.inline.} =
|
||||
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())
|
||||
|
||||
proc setColor*(image: Image, x, y: int, color: Color) =
|
||||
proc setColor*(image: Image, x, y: int, color: Color) {.inline, raises: [].} =
|
||||
## Sets a color at (x, y) or does nothing if outside of bounds.
|
||||
image[x, y] = color.rgbx()
|
||||
|
||||
proc fillUnsafe*(data: var seq[ColorRGBX], color: SomeColor, start, len: int) =
|
||||
proc fillUnsafe*(
|
||||
data: var seq[ColorRGBX], color: SomeColor, start, len: int
|
||||
) {.raises: [].} =
|
||||
## Fills the image data with the parameter color starting at index start and
|
||||
## continuing for len indices.
|
||||
|
||||
|
@ -122,11 +126,11 @@ proc fillUnsafe*(data: var seq[ColorRGBX], color: SomeColor, start, len: int) =
|
|||
for j in i ..< start + len:
|
||||
data[j] = rgbx
|
||||
|
||||
proc fill*(image: Image, color: SomeColor) {.inline.} =
|
||||
proc fill*(image: Image, color: SomeColor) {.inline, raises: [].} =
|
||||
## Fills the image with the parameter color.
|
||||
fillUnsafe(image.data, color, 0, image.data.len)
|
||||
|
||||
proc isOneColor*(image: Image): bool =
|
||||
proc isOneColor*(image: Image): bool {.raises: [].} =
|
||||
## Checks if the entire image is the same color.
|
||||
result = true
|
||||
|
||||
|
@ -149,7 +153,7 @@ proc isOneColor*(image: Image): bool =
|
|||
if image.data[j] != color:
|
||||
return false
|
||||
|
||||
proc isTransparent*(image: Image): bool =
|
||||
proc isTransparent*(image: Image): bool {.raises: [].} =
|
||||
## Checks if this image is fully transparent or not.
|
||||
result = true
|
||||
|
||||
|
@ -174,7 +178,7 @@ proc isTransparent*(image: Image): bool =
|
|||
if image.data[j].a != 0:
|
||||
return false
|
||||
|
||||
proc flipHorizontal*(image: Image) =
|
||||
proc flipHorizontal*(image: Image) {.raises: [].} =
|
||||
## Flips the image around the Y axis.
|
||||
let w = image.width div 2
|
||||
for y in 0 ..< image.height:
|
||||
|
@ -185,7 +189,7 @@ proc flipHorizontal*(image: Image) =
|
|||
image.setRgbaUnsafe(image.width - x - 1, y, rgba1)
|
||||
image.setRgbaUnsafe(x, y, rgba2)
|
||||
|
||||
proc flipVertical*(image: Image) =
|
||||
proc flipVertical*(image: Image) {.raises: [].} =
|
||||
## Flips the image around the X axis.
|
||||
let h = image.height div 2
|
||||
for y in 0 ..< h:
|
||||
|
@ -196,7 +200,7 @@ proc flipVertical*(image: Image) =
|
|||
image.setRgbaUnsafe(x, image.height - y - 1, rgba1)
|
||||
image.setRgbaUnsafe(x, y, rgba2)
|
||||
|
||||
proc subImage*(image: Image, x, y, w, h: int): Image =
|
||||
proc subImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} =
|
||||
## Gets a sub image from this image.
|
||||
|
||||
if x < 0 or x + w > image.width:
|
||||
|
@ -218,7 +222,7 @@ proc subImage*(image: Image, x, y, w, h: int): Image =
|
|||
w * 4
|
||||
)
|
||||
|
||||
proc diff*(master, image: Image): (float32, Image) =
|
||||
proc diff*(master, image: Image): (float32, Image) {.raises: [PixieError].} =
|
||||
## Compares the parameters and returns a score and image of the difference.
|
||||
let
|
||||
w = max(master.width, image.width)
|
||||
|
@ -248,7 +252,7 @@ proc diff*(master, image: Image): (float32, Image) =
|
|||
|
||||
(100 * diffScore.float32 / diffTotal.float32, diffImage)
|
||||
|
||||
proc minifyBy2*(image: Image, power = 1): Image =
|
||||
proc minifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
|
||||
## Scales the image down by an integer scale.
|
||||
if power < 0:
|
||||
raise newException(PixieError, "Cannot minifyBy2 with negative power")
|
||||
|
@ -323,7 +327,7 @@ proc minifyBy2*(image: Image, power = 1): Image =
|
|||
# Set src as this result for if we do another power
|
||||
src = result
|
||||
|
||||
proc magnifyBy2*(image: Image, power = 1): Image =
|
||||
proc magnifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} =
|
||||
## Scales image up by 2 ^ power.
|
||||
if power < 0:
|
||||
raise newException(PixieError, "Cannot magnifyBy2 with negative power")
|
||||
|
@ -339,7 +343,7 @@ proc magnifyBy2*(image: Image, power = 1): Image =
|
|||
for i in 0 ..< scale:
|
||||
result.data[idx + i] = rgba
|
||||
|
||||
proc applyOpacity*(target: Image | Mask, opacity: float32) =
|
||||
proc applyOpacity*(target: Image | Mask, opacity: float32) {.raises: [].} =
|
||||
## Multiplies alpha of the image by opacity.
|
||||
let opacity = round(255 * opacity).uint16
|
||||
|
||||
|
@ -405,7 +409,7 @@ proc applyOpacity*(target: Image | Mask, opacity: float32) =
|
|||
for j in i ..< target.data.len:
|
||||
target.data[j] = ((target.data[j] * opacity) div 255).uint8
|
||||
|
||||
proc invert*(target: Image | Mask) =
|
||||
proc invert*(target: Image | Mask) {.raises: [].} =
|
||||
## Inverts all of the colors and alpha.
|
||||
var i: int
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
|
@ -447,7 +451,7 @@ proc invert*(target: Image | Mask) =
|
|||
|
||||
proc blur*(
|
||||
image: Image, radius: float32, outOfBounds: SomeColor = ColorRGBX()
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Applies Gaussian blur to the image given a radius.
|
||||
let radius = round(radius).int
|
||||
if radius == 0:
|
||||
|
@ -512,7 +516,7 @@ proc blur*(
|
|||
|
||||
image.setRgbaUnsafe(x, y, rgbx(values))
|
||||
|
||||
proc newMask*(image: Image): Mask =
|
||||
proc newMask*(image: Image): Mask {.raises: [PixieError].} =
|
||||
## Returns a new mask using the alpha values of the parameter image.
|
||||
result = newMask(image.width, image.height)
|
||||
|
||||
|
@ -544,7 +548,9 @@ proc newMask*(image: Image): Mask =
|
|||
for j in i ..< image.data.len:
|
||||
result.data[j] = image.data[j].a
|
||||
|
||||
proc getRgbaSmooth*(image: Image, x, y: float32, wrapped = false): ColorRGBX =
|
||||
proc getRgbaSmooth*(
|
||||
image: Image, x, y: float32, wrapped = false
|
||||
): ColorRGBX {.raises: [].} =
|
||||
## Gets a interpolated color with float point coordinates.
|
||||
## Pixes outside the image are transparent.
|
||||
let
|
||||
|
@ -582,7 +588,7 @@ proc getRgbaSmooth*(image: Image, x, y: float32, wrapped = false): ColorRGBX =
|
|||
|
||||
proc drawCorrect(
|
||||
a, b: Image | Mask, mat = mat3(), tiled = false, blendMode = bmNormal
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Draws one image onto another using matrix with color blending.
|
||||
|
||||
when type(a) is Image:
|
||||
|
@ -634,7 +640,9 @@ proc drawCorrect(
|
|||
let sample = b.getValueSmooth(xFloat, yFloat)
|
||||
a.setValueUnsafe(x, y, masker(backdrop, sample))
|
||||
|
||||
proc drawUber(a, b: Image | Mask, mat = mat3(), blendMode = bmNormal) =
|
||||
proc drawUber(
|
||||
a, b: Image | Mask, mat = mat3(), blendMode = bmNormal
|
||||
) {.raises: [PixieError].} =
|
||||
let
|
||||
corners = [
|
||||
mat * vec2(0, 0),
|
||||
|
@ -840,7 +848,7 @@ proc drawUber(a, b: Image | Mask, mat = mat3(), blendMode = bmNormal) =
|
|||
|
||||
proc draw*(
|
||||
a, b: Image, transform = mat3(), blendMode = bmNormal
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws one image onto another using matrix with color blending.
|
||||
when type(transform) is Vec2:
|
||||
a.drawUber(b, translate(transform), blendMode)
|
||||
|
@ -849,7 +857,7 @@ proc draw*(
|
|||
|
||||
proc draw*(
|
||||
a, b: Mask, transform = mat3(), blendMode = bmMask
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a mask onto a mask using a matrix with color blending.
|
||||
when type(transform) is Vec2:
|
||||
a.drawUber(b, translate(transform), blendMode)
|
||||
|
@ -858,7 +866,7 @@ proc draw*(
|
|||
|
||||
proc draw*(
|
||||
image: Image, mask: Mask, transform = mat3(), blendMode = bmMask
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a mask onto an image using a matrix with color blending.
|
||||
when type(transform) is Vec2:
|
||||
image.drawUber(mask, translate(transform), blendMode)
|
||||
|
@ -867,17 +875,19 @@ proc draw*(
|
|||
|
||||
proc draw*(
|
||||
mask: Mask, image: Image, transform = mat3(), blendMode = bmMask
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Draws a image onto a mask using a matrix with color blending.
|
||||
when type(transform) is Vec2:
|
||||
mask.drawUber(image, translate(transform), blendMode)
|
||||
else:
|
||||
mask.drawUber(image, transform, blendMode)
|
||||
|
||||
proc drawTiled*(dest, src: Image, mat: Mat3, blendMode = bmNormal) =
|
||||
dest.drawCorrect(src, mat, true, blendMode)
|
||||
proc drawTiled*(
|
||||
dst, src: Image, mat: Mat3, blendMode = bmNormal
|
||||
) {.raises: [PixieError]} =
|
||||
dst.drawCorrect(src, mat, true, blendMode)
|
||||
|
||||
proc resize*(srcImage: Image, width, height: int): Image =
|
||||
proc resize*(srcImage: Image, width, height: int): Image {.raises: [PixieError]} =
|
||||
## Resize an image to a given height and width.
|
||||
if width == srcImage.width and height == srcImage.height:
|
||||
result = srcImage.copy()
|
||||
|
@ -894,7 +904,7 @@ proc resize*(srcImage: Image, width, height: int): Image =
|
|||
|
||||
proc shadow*(
|
||||
image: Image, offset: Vec2, spread, blur: float32, color: SomeColor
|
||||
): Image =
|
||||
): Image {.raises: [PixieError]} =
|
||||
## Create a shadow of the image with the offset, spread and blur.
|
||||
let
|
||||
mask = image.newMask()
|
||||
|
@ -906,7 +916,7 @@ proc shadow*(
|
|||
result.fill(color)
|
||||
result.draw(shifted, blendMode = bmMask)
|
||||
|
||||
proc superImage*(image: Image, x, y, w, h: int): Image =
|
||||
proc superImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError]} =
|
||||
## Either cuts a sub image or returns a super image with padded transparency.
|
||||
if x >= 0 and x + w <= image.width and y >= 0 and y + h <= image.height:
|
||||
result = image.subImage(x, y, w, h)
|
||||
|
|
|
@ -12,7 +12,7 @@ type
|
|||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc newMask*(width, height: int): Mask =
|
||||
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")
|
||||
|
@ -22,59 +22,59 @@ proc newMask*(width, height: int): Mask =
|
|||
result.height = height
|
||||
result.data = newSeq[uint8](width * height)
|
||||
|
||||
proc wh*(mask: Mask): Vec2 {.inline.} =
|
||||
proc wh*(mask: Mask): Vec2 {.inline, raises: [].} =
|
||||
## Return with and height as a size vector.
|
||||
vec2(mask.width.float32, mask.height.float32)
|
||||
|
||||
proc copy*(mask: Mask): Mask =
|
||||
proc copy*(mask: Mask): Mask {.raises: [PixieError].} =
|
||||
## Copies the image data into a new image.
|
||||
result = newMask(mask.width, mask.height)
|
||||
result.data = mask.data
|
||||
|
||||
proc `$`*(mask: Mask): string =
|
||||
proc `$`*(mask: Mask): string {.raises: [].} =
|
||||
## Prints the mask size.
|
||||
"<Mask " & $mask.width & "x" & $mask.height & ">"
|
||||
|
||||
proc inside*(mask: Mask, x, y: int): bool {.inline.} =
|
||||
proc inside*(mask: Mask, x, y: int): bool {.inline, raises: [].} =
|
||||
## Returns true if (x, y) is inside the mask.
|
||||
x >= 0 and x < mask.width and y >= 0 and y < mask.height
|
||||
|
||||
proc dataIndex*(mask: Mask, x, y: int): int {.inline.} =
|
||||
proc dataIndex*(mask: Mask, x, y: int): int {.inline, raises: [].} =
|
||||
mask.width * y + x
|
||||
|
||||
proc getValueUnsafe*(mask: Mask, x, y: int): uint8 {.inline.} =
|
||||
proc getValueUnsafe*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
|
||||
## 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.width * y + x]
|
||||
|
||||
proc `[]`*(mask: Mask, x, y: int): uint8 {.inline.} =
|
||||
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)
|
||||
|
||||
proc getValue*(mask: Mask, x, y: int): uint8 {.inline.} =
|
||||
proc getValue*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} =
|
||||
## Gets a value at (x, y) or returns transparent black if outside of bounds.
|
||||
mask[x, y]
|
||||
|
||||
proc setValueUnsafe*(mask: Mask, x, y: int, value: uint8) {.inline.} =
|
||||
proc setValueUnsafe*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} =
|
||||
## 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
|
||||
|
||||
proc `[]=`*(mask: Mask, x, y: int, value: uint8) {.inline.} =
|
||||
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)
|
||||
|
||||
proc setValue*(mask: Mask, x, y: int, value: uint8) {.inline.} =
|
||||
proc setValue*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} =
|
||||
## Sets a value at (x, y) or does nothing if outside of bounds.
|
||||
mask[x, y] = value
|
||||
|
||||
proc minifyBy2*(mask: Mask, power = 1): Mask =
|
||||
proc minifyBy2*(mask: Mask, power = 1): Mask {.raises: [PixieError].} =
|
||||
## Scales the mask down by an integer scale.
|
||||
if power < 0:
|
||||
raise newException(PixieError, "Cannot minifyBy2 with negative power")
|
||||
|
@ -157,16 +157,18 @@ proc minifyBy2*(mask: Mask, power = 1): Mask =
|
|||
# Set src as this result for if we do another power
|
||||
src = result
|
||||
|
||||
proc fillUnsafe*(data: var seq[uint8], value: uint8, start, len: int) =
|
||||
proc fillUnsafe*(
|
||||
data: var seq[uint8], value: uint8, start, len: int
|
||||
) {.raises: [].} =
|
||||
## Fills the mask data with the parameter value starting at index start and
|
||||
## continuing for len indices.
|
||||
nimSetMem(data[start].addr, value.cint, len)
|
||||
|
||||
proc fill*(mask: Mask, value: uint8) {.inline.} =
|
||||
proc fill*(mask: Mask, value: uint8) {.inline, raises: [].} =
|
||||
## Fills the mask with the parameter value.
|
||||
fillUnsafe(mask.data, value, 0, mask.data.len)
|
||||
|
||||
proc getValueSmooth*(mask: Mask, x, y: float32): uint8 =
|
||||
proc getValueSmooth*(mask: Mask, x, y: float32): uint8 {.raises: [].} =
|
||||
## Gets a interpolated value with float point coordinates.
|
||||
let
|
||||
x0 = x.int
|
||||
|
@ -194,7 +196,7 @@ proc getValueSmooth*(mask: Mask, x, y: float32): uint8 =
|
|||
else:
|
||||
topMix
|
||||
|
||||
proc spread*(mask: Mask, spread: float32) =
|
||||
proc spread*(mask: Mask, spread: float32) {.raises: [PixieError].} =
|
||||
## Grows the mask by spread.
|
||||
let spread = round(spread).int
|
||||
if spread == 0:
|
||||
|
@ -227,7 +229,7 @@ proc spread*(mask: Mask, spread: float32) =
|
|||
break
|
||||
mask.setValueUnsafe(x, y, maxValue)
|
||||
|
||||
proc ceil*(mask: Mask) =
|
||||
proc ceil*(mask: Mask) {.raises: [].} =
|
||||
## A value of 0 stays 0. Anything else turns into 255.
|
||||
var i: int
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
|
@ -245,7 +247,7 @@ proc ceil*(mask: Mask) =
|
|||
if mask.data[j] != 0:
|
||||
mask.data[j] = 255
|
||||
|
||||
proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) =
|
||||
proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) {.raises: [PixieError].} =
|
||||
## Applies Gaussian blur to the image given a radius.
|
||||
let radius = round(radius).int
|
||||
if radius == 0:
|
||||
|
|
|
@ -15,7 +15,7 @@ type
|
|||
blendMode*: BlendMode ## Blend mode.
|
||||
opacity*: float32
|
||||
# pkSolid
|
||||
color*: Color ## Color to fill with.
|
||||
color*: Color ## Color to fill with.
|
||||
# pkImage, pkImageTiled:
|
||||
image*: Image ## Image to fill with.
|
||||
imageMat*: Mat3 ## Matrix of the filled image.
|
||||
|
@ -25,16 +25,16 @@ type
|
|||
|
||||
ColorStop* = object
|
||||
## Color stop on a gradient curve.
|
||||
color*: Color ## Color of the stop.
|
||||
color*: Color ## Color of the stop.
|
||||
position*: float32 ## Gradient stop position 0..1.
|
||||
|
||||
SomePaint* = string | Paint | SomeColor
|
||||
|
||||
proc newPaint*(kind: PaintKind): Paint =
|
||||
proc newPaint*(kind: PaintKind): Paint {.raises: [].} =
|
||||
## Create a new Paint.
|
||||
result = Paint(kind: kind, opacity: 1, imageMat: mat3())
|
||||
|
||||
proc newPaint*(paint: Paint): Paint =
|
||||
proc newPaint*(paint: Paint): Paint {.raises: [].} =
|
||||
## Create a new Paint with the same properties.
|
||||
result = newPaint(paint.kind)
|
||||
result.blendMode = paint.blendMode
|
||||
|
@ -45,11 +45,16 @@ proc newPaint*(paint: Paint): Paint =
|
|||
result.gradientHandlePositions = paint.gradientHandlePositions
|
||||
result.gradientStops = paint.gradientStops
|
||||
|
||||
converter parseSomePaint*(paint: SomePaint): Paint {.inline.} =
|
||||
converter parseSomePaint*(
|
||||
paint: SomePaint
|
||||
): Paint {.inline, raises: [PixieError].} =
|
||||
## Given SomePaint, parse it in different ways.
|
||||
when type(paint) is string:
|
||||
result = newPaint(pkSolid)
|
||||
result.color = parseHtmlColor(paint)
|
||||
try:
|
||||
result.color = parseHtmlColor(paint)
|
||||
except:
|
||||
raise newException(PixieError, "Unable to parse color " & paint)
|
||||
elif type(paint) is SomeColor:
|
||||
result = newPaint(pkSolid)
|
||||
when type(paint) is Color:
|
||||
|
@ -59,7 +64,7 @@ converter parseSomePaint*(paint: SomePaint): Paint {.inline.} =
|
|||
elif type(paint) is Paint:
|
||||
paint
|
||||
|
||||
proc toLineSpace(at, to, point: Vec2): float32 {.inline.} =
|
||||
proc toLineSpace(at, to, point: Vec2): float32 {.inline, raises: [].} =
|
||||
## Convert position on to where it would fall on a line between at and to.
|
||||
let
|
||||
d = to - at
|
||||
|
@ -68,7 +73,7 @@ proc toLineSpace(at, to, point: Vec2): float32 {.inline.} =
|
|||
|
||||
proc gradientPut(
|
||||
image: Image, paint: Paint, x, y: int, t: float32, stops: seq[ColorStop]
|
||||
) =
|
||||
) {.raises: [].} =
|
||||
## Put an gradient color based on `t` - where are we related to a line.
|
||||
var index = -1
|
||||
for i, stop in stops:
|
||||
|
@ -95,7 +100,7 @@ proc gradientPut(
|
|||
color.a *= paint.opacity
|
||||
image.setRgbaUnsafe(x, y, color.rgbx())
|
||||
|
||||
proc fillGradientLinear(image: Image, paint: Paint) =
|
||||
proc fillGradientLinear(image: Image, paint: Paint) {.raises: [PixieError].} =
|
||||
## Fills a linear gradient.
|
||||
|
||||
if paint.gradientHandlePositions.len != 2:
|
||||
|
@ -117,7 +122,7 @@ proc fillGradientLinear(image: Image, paint: Paint) =
|
|||
t = toLineSpace(at, to, xy)
|
||||
image.gradientPut(paint, x, y, t, paint.gradientStops)
|
||||
|
||||
proc fillGradientRadial(image: Image, paint: Paint) =
|
||||
proc fillGradientRadial(image: Image, paint: Paint) {.raises: [PixieError].} =
|
||||
## Fills a radial gradient.
|
||||
|
||||
if paint.gradientHandlePositions.len != 3:
|
||||
|
@ -148,7 +153,7 @@ proc fillGradientRadial(image: Image, paint: Paint) =
|
|||
t = (mat * xy).length()
|
||||
image.gradientPut(paint, x, y, t, paint.gradientStops)
|
||||
|
||||
proc fillGradientAngular(image: Image, paint: Paint) =
|
||||
proc fillGradientAngular(image: Image, paint: Paint) {.raises: [PixieError].} =
|
||||
## Fills an angular gradient.
|
||||
|
||||
if paint.gradientHandlePositions.len != 3:
|
||||
|
@ -173,7 +178,7 @@ proc fillGradientAngular(image: Image, paint: Paint) =
|
|||
t = (angle + gradientAngle + PI / 2).fixAngle() / 2 / PI + 0.5
|
||||
image.gradientPut(paint, x, y, t, paint.gradientStops)
|
||||
|
||||
proc fillGradient*(image: Image, paint: Paint) =
|
||||
proc fillGradient*(image: Image, paint: Paint) {.raises: [PixieError].} =
|
||||
## Fills with the Paint gradient.
|
||||
|
||||
case paint.kind:
|
||||
|
|
|
@ -46,23 +46,23 @@ const
|
|||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc newPath*(): Path =
|
||||
proc newPath*(): Path {.raises: [].} =
|
||||
## Create a new Path.
|
||||
Path()
|
||||
|
||||
proc pixelScale(transform: Mat3): float32 =
|
||||
proc pixelScale(transform: Mat3): float32 {.raises: [].} =
|
||||
## What is the largest scale factor of this transform?
|
||||
max(
|
||||
vec2(transform[0, 0], transform[0, 1]).length,
|
||||
vec2(transform[1, 0], transform[1, 1]).length
|
||||
)
|
||||
|
||||
proc isRelative(kind: PathCommandKind): bool =
|
||||
proc isRelative(kind: PathCommandKind): bool {.inline, raises: [].} =
|
||||
kind in {
|
||||
RMove, RLine, TQuad, RTQuad, RHLine, RVLine, RCubic, RSCubic, RQuad, RArc
|
||||
}
|
||||
|
||||
proc parameterCount(kind: PathCommandKind): int =
|
||||
proc parameterCount(kind: PathCommandKind): int {.raises: [].} =
|
||||
## Returns number of parameters a path command has.
|
||||
case kind:
|
||||
of Close: 0
|
||||
|
@ -72,7 +72,7 @@ proc parameterCount(kind: PathCommandKind): int =
|
|||
of SCubic, RSCubic, Quad, RQuad: 4
|
||||
of Arc, RArc: 7
|
||||
|
||||
proc `$`*(path: Path): string =
|
||||
proc `$`*(path: Path): string {.raises: [].} =
|
||||
## Turn path int into a string.
|
||||
for i, command in path.commands:
|
||||
case command.kind
|
||||
|
@ -103,7 +103,7 @@ proc `$`*(path: Path): string =
|
|||
if i != path.commands.len - 1 or j != command.numbers.len - 1:
|
||||
result.add " "
|
||||
|
||||
proc parsePath*(path: string): Path =
|
||||
proc parsePath*(path: string): Path {.raises: [PixieError].} =
|
||||
## Converts a SVG style path string into seq of commands.
|
||||
result = newPath()
|
||||
|
||||
|
@ -249,7 +249,7 @@ proc parsePath*(path: string): Path =
|
|||
|
||||
finishCommand(result)
|
||||
|
||||
proc transform*(path: Path, mat: Mat3) =
|
||||
proc transform*(path: Path, mat: Mat3) {.raises: [].} =
|
||||
## Apply a matrix transform to a path.
|
||||
if mat == mat3():
|
||||
return
|
||||
|
@ -314,39 +314,39 @@ proc transform*(path: Path, mat: Mat3) =
|
|||
command.numbers[5] = to.x
|
||||
command.numbers[6] = to.y
|
||||
|
||||
proc addPath*(path: Path, other: Path) =
|
||||
proc addPath*(path: Path, other: Path) {.raises: [].} =
|
||||
## Adds a path to the current path.
|
||||
path.commands.add(other.commands)
|
||||
|
||||
proc closePath*(path: Path) =
|
||||
proc closePath*(path: Path) {.raises: [].} =
|
||||
## Attempts to add a straight line from the current point to the start of
|
||||
## the current sub-path. If the shape has already been closed or has only
|
||||
## one point, this function does nothing.
|
||||
path.commands.add(PathCommand(kind: Close))
|
||||
path.at = path.start
|
||||
|
||||
proc moveTo*(path: Path, x, y: float32) =
|
||||
proc moveTo*(path: Path, x, y: float32) {.raises: [].} =
|
||||
## Begins a new sub-path at the point (x, y).
|
||||
path.commands.add(PathCommand(kind: Move, numbers: @[x, y]))
|
||||
path.start = vec2(x, y)
|
||||
path.at = path.start
|
||||
|
||||
proc moveTo*(path: Path, v: Vec2) {.inline.} =
|
||||
proc moveTo*(path: Path, v: Vec2) {.inline, raises: [].} =
|
||||
## Begins a new sub-path at the point (x, y).
|
||||
path.moveTo(v.x, v.y)
|
||||
|
||||
proc lineTo*(path: Path, x, y: float32) =
|
||||
proc lineTo*(path: Path, x, y: float32) {.raises: [].} =
|
||||
## Adds a straight line to the current sub-path by connecting the sub-path's
|
||||
## last point to the specified (x, y) coordinates.
|
||||
path.commands.add(PathCommand(kind: Line, numbers: @[x, y]))
|
||||
path.at = vec2(x, y)
|
||||
|
||||
proc lineTo*(path: Path, v: Vec2) {.inline.} =
|
||||
proc lineTo*(path: Path, v: Vec2) {.inline, raises: [].} =
|
||||
## Adds a straight line to the current sub-path by connecting the sub-path's
|
||||
## last point to the specified (x, y) coordinates.
|
||||
path.lineTo(v.x, v.y)
|
||||
|
||||
proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) =
|
||||
proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) {.raises: [].} =
|
||||
## Adds a cubic Bézier curve to the current sub-path. It requires three
|
||||
## points: the first two are control points and the third one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
|
@ -357,14 +357,14 @@ proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) =
|
|||
))
|
||||
path.at = vec2(x3, y3)
|
||||
|
||||
proc bezierCurveTo*(path: Path, ctrl1, ctrl2, to: Vec2) {.inline.} =
|
||||
proc bezierCurveTo*(path: Path, ctrl1, ctrl2, to: Vec2) {.inline, raises: [].} =
|
||||
## Adds a cubic Bézier curve to the current sub-path. It requires three
|
||||
## points: the first two are control points and the third one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
## which can be changed using moveTo() before creating the Bézier curve.
|
||||
path.bezierCurveTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y)
|
||||
|
||||
proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) =
|
||||
proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) {.raises: [].} =
|
||||
## Adds a quadratic Bézier curve to the current sub-path. It requires two
|
||||
## points: the first one is a control point and the second one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
|
@ -376,7 +376,7 @@ proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) =
|
|||
))
|
||||
path.at = vec2(x2, y2)
|
||||
|
||||
proc quadraticCurveTo*(path: Path, ctrl, to: Vec2) {.inline.} =
|
||||
proc quadraticCurveTo*(path: Path, ctrl, to: Vec2) {.inline, raises: [].} =
|
||||
## Adds a quadratic Bézier curve to the current sub-path. It requires two
|
||||
## points: the first one is a control point and the second one is the end
|
||||
## point. The starting point is the latest point in the current path,
|
||||
|
@ -390,7 +390,7 @@ proc ellipticalArcTo*(
|
|||
xAxisRotation: float32,
|
||||
largeArcFlag, sweepFlag: bool,
|
||||
x, y: float32
|
||||
) =
|
||||
) {.raises: [].} =
|
||||
## Adds an elliptical arc to the current sub-path, using the given radius
|
||||
## ratios, sweep flags, and end position.
|
||||
path.commands.add(PathCommand(
|
||||
|
@ -401,7 +401,9 @@ proc ellipticalArcTo*(
|
|||
))
|
||||
path.at = vec2(x, y)
|
||||
|
||||
proc arc*(path: Path, x, y, r, a0, a1: float32, ccw: bool) =
|
||||
proc arc*(
|
||||
path: Path, x, y, r, a0, a1: float32, ccw: bool
|
||||
) {.raises: [PixieError].} =
|
||||
## Adds a circular arc to the current sub-path.
|
||||
if r == 0: # When radius is zero, do nothing.
|
||||
return
|
||||
|
@ -438,11 +440,13 @@ proc arc*(path: Path, x, y, r, a0, a1: float32, ccw: bool) =
|
|||
path.at.y = y + r * sin(a1)
|
||||
path.ellipticalArcTo(r, r, 0, angle >= PI, cw, path.at.x, path.at.y)
|
||||
|
||||
proc arc*(path: Path, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) =
|
||||
proc arc*(
|
||||
path: Path, pos: Vec2, r: float32, a: Vec2, ccw: bool = false
|
||||
) {.inline, raises: [PixieError].} =
|
||||
## Adds a circular arc to the current sub-path.
|
||||
path.arc(pos.x, pos.y, r, a.x, a.y, ccw)
|
||||
|
||||
proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) =
|
||||
proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) {.raises: [PixieError].} =
|
||||
## Adds a circular arc using the given control points and radius.
|
||||
## Commonly used for making rounded corners.
|
||||
if r < 0: # When radius is negative, error.
|
||||
|
@ -483,11 +487,11 @@ proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) =
|
|||
path.at.y = y1 + t21 * y21
|
||||
path.ellipticalArcTo(r, r, 0, false, y01 * x20 > x01 * y20, path.at.x, path.at.y)
|
||||
|
||||
proc arcTo*(path: Path, a, b: Vec2, r: float32) =
|
||||
proc arcTo*(path: Path, a, b: Vec2, r: float32) {.inline, raises: [PixieError].} =
|
||||
## Adds a circular arc using the given control points and radius.
|
||||
path.arcTo(a.x, a.y, b.x, b.y, r)
|
||||
|
||||
proc rect*(path: Path, x, y, w, h: float32, clockwise = true) =
|
||||
proc rect*(path: Path, x, y, w, h: float32, clockwise = true) {.raises: [].} =
|
||||
## Adds a rectangle.
|
||||
## Clockwise param can be used to subtract a rect from a path when using
|
||||
## even-odd winding rule.
|
||||
|
@ -504,7 +508,7 @@ proc rect*(path: Path, x, y, w, h: float32, clockwise = true) =
|
|||
path.lineTo(x + w, y)
|
||||
path.closePath()
|
||||
|
||||
proc rect*(path: Path, rect: Rect, clockwise = true) {.inline.} =
|
||||
proc rect*(path: Path, rect: Rect, clockwise = true) {.inline, raises: [].} =
|
||||
## Adds a rectangle.
|
||||
## Clockwise param can be used to subtract a rect from a path when using
|
||||
## even-odd winding rule.
|
||||
|
@ -516,7 +520,7 @@ const splineCircleK = 4.0 * (-1.0 + sqrt(2.0)) / 3
|
|||
|
||||
proc roundedRect*(
|
||||
path: Path, x, y, w, h, nw, ne, se, sw: float32, clockwise = true
|
||||
) =
|
||||
) {.raises: [].} =
|
||||
## Adds a rounded rectangle.
|
||||
## Clockwise param can be used to subtract a rect from a path when using
|
||||
## even-odd winding rule.
|
||||
|
@ -583,13 +587,13 @@ proc roundedRect*(
|
|||
|
||||
proc roundedRect*(
|
||||
path: Path, rect: Rect, nw, ne, se, sw: float32, clockwise = true
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a rounded rectangle.
|
||||
## Clockwise param can be used to subtract a rect from a path when using
|
||||
## even-odd winding rule.
|
||||
path.roundedRect(rect.x, rect.y, rect.w, rect.h, nw, ne, se, sw, clockwise)
|
||||
|
||||
proc ellipse*(path: Path, cx, cy, rx, ry: float32) =
|
||||
proc ellipse*(path: Path, cx, cy, rx, ry: float32) {.raises: [].} =
|
||||
## Adds a ellipse.
|
||||
let
|
||||
magicX = splineCircleK * rx
|
||||
|
@ -602,19 +606,19 @@ proc ellipse*(path: Path, cx, cy, rx, ry: float32) =
|
|||
path.bezierCurveTo(cx + magicX, cy - ry, cx + rx, cy - magicY, cx + rx, cy)
|
||||
path.closePath()
|
||||
|
||||
proc ellipse*(path: Path, center: Vec2, rx, ry: float32) {.inline.} =
|
||||
proc ellipse*(path: Path, center: Vec2, rx, ry: float32) {.inline, raises: [].} =
|
||||
## Adds a ellipse.
|
||||
path.ellipse(center.x, center.y, rx, ry)
|
||||
|
||||
proc circle*(path: Path, cx, cy, r: float32) {.inline.} =
|
||||
proc circle*(path: Path, cx, cy, r: float32) {.inline, raises: [].} =
|
||||
## Adds a circle.
|
||||
path.ellipse(cx, cy, r, r)
|
||||
|
||||
proc circle*(path: Path, circle: Circle) {.inline.} =
|
||||
proc circle*(path: Path, circle: Circle) {.inline, raises: [].} =
|
||||
## Adds a circle.
|
||||
path.ellipse(circle.pos.x, circle.pos.y, circle.radius, circle.radius)
|
||||
|
||||
proc polygon*(path: Path, x, y, size: float32, sides: int) =
|
||||
proc polygon*(path: Path, x, y, size: float32, sides: int) {.raises: [].} =
|
||||
## Adds an n-sided regular polygon at (x, y) with the parameter size.
|
||||
path.moveTo(x + size * cos(0.0), y + size * sin(0.0))
|
||||
for side in 0 .. sides:
|
||||
|
@ -623,13 +627,15 @@ proc polygon*(path: Path, x, y, size: float32, sides: int) =
|
|||
y + size * sin(side.float32 * 2.0 * PI / sides.float32)
|
||||
)
|
||||
|
||||
proc polygon*(path: Path, pos: Vec2, size: float32, sides: int) {.inline.} =
|
||||
proc polygon*(
|
||||
path: Path, pos: Vec2, size: float32, sides: int
|
||||
) {.inline, raises: [].} =
|
||||
## Adds a n-sided regular polygon at (x, y) with the parameter size.
|
||||
path.polygon(pos.x, pos.y, size, sides)
|
||||
|
||||
proc commandsToShapes(
|
||||
path: Path, closeSubpaths = false, pixelScale: float32 = 1.0
|
||||
): seq[seq[Vec2]] =
|
||||
): seq[seq[Vec2]] {.raises: [PixieError].} =
|
||||
## Converts SVG-like commands to sequences of vectors.
|
||||
var
|
||||
start, at: Vec2
|
||||
|
@ -991,7 +997,9 @@ proc commandsToShapes(
|
|||
shape.addSegment(at, start)
|
||||
result.add(shape)
|
||||
|
||||
proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] =
|
||||
proc shapesToSegments(
|
||||
shapes: seq[seq[Vec2]]
|
||||
): seq[(Segment, int16)] {.raises: [].} =
|
||||
## Converts the shapes into a set of filtered segments with winding value.
|
||||
for shape in shapes:
|
||||
for segment in shape.segments:
|
||||
|
@ -1006,7 +1014,7 @@ proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] =
|
|||
|
||||
result.add((segment, winding))
|
||||
|
||||
proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool =
|
||||
proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool {.raises: [].} =
|
||||
## Returns true if the fill requires antialiasing.
|
||||
|
||||
template hasFractional(v: float32): bool =
|
||||
|
@ -1021,13 +1029,13 @@ proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool =
|
|||
# AA is required if all segments are not vertical or have fractional > 0
|
||||
return true
|
||||
|
||||
proc transform(shapes: var seq[seq[Vec2]], transform: Mat3) =
|
||||
proc transform(shapes: var seq[seq[Vec2]], transform: Mat3) {.raises: [].} =
|
||||
if transform != mat3():
|
||||
for shape in shapes.mitems:
|
||||
for vec in shape.mitems:
|
||||
vec = transform * vec
|
||||
|
||||
proc computeBounds(segments: seq[(Segment, int16)]): Rect =
|
||||
proc computeBounds(segments: seq[(Segment, int16)]): Rect {.raises: [].} =
|
||||
## Compute the bounds of the segments.
|
||||
var
|
||||
xMin = float32.high
|
||||
|
@ -1049,7 +1057,9 @@ proc computeBounds(segments: seq[(Segment, int16)]): Rect =
|
|||
result.w = xMax - xMin
|
||||
result.h = yMax - yMin
|
||||
|
||||
proc computeBounds*(path: Path, transform = mat3()): Rect =
|
||||
proc computeBounds*(
|
||||
path: Path, transform = mat3()
|
||||
): Rect {.raises: [PixieError].} =
|
||||
## Compute the bounds of the path.
|
||||
var shapes = path.commandsToShapes()
|
||||
shapes.transform(transform)
|
||||
|
@ -1057,7 +1067,7 @@ proc computeBounds*(path: Path, transform = mat3()): Rect =
|
|||
|
||||
proc partitionSegments(
|
||||
segments: seq[(Segment, int16)], top, height: int
|
||||
): Partitioning =
|
||||
): Partitioning {.raises: [].} =
|
||||
## Puts segments into the height partitions they intersect with.
|
||||
let
|
||||
maxPartitions = max(1, height div 10).uint32
|
||||
|
@ -1081,7 +1091,9 @@ proc partitionSegments(
|
|||
for i in atPartition .. toPartition:
|
||||
result.partitions[i].add((segment, winding))
|
||||
|
||||
proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} =
|
||||
proc getIndexForY(
|
||||
partitioning: Partitioning, y: int
|
||||
): uint32 {.inline, raises: [].} =
|
||||
if partitioning.partitionHeight == 0 or partitioning.partitions.len == 1:
|
||||
0.uint32
|
||||
else:
|
||||
|
@ -1090,7 +1102,9 @@ proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} =
|
|||
partitioning.partitions.high.uint32
|
||||
)
|
||||
|
||||
proc insertionSort(a: var seq[(float32, int16)], lo, hi: int) {.inline.} =
|
||||
proc insertionSort(
|
||||
a: var seq[(float32, int16)], lo, hi: int
|
||||
) {.inline, raises: [].} =
|
||||
for i in lo + 1 .. hi:
|
||||
var
|
||||
j = i - 1
|
||||
|
@ -1100,7 +1114,7 @@ proc insertionSort(a: var seq[(float32, int16)], lo, hi: int) {.inline.} =
|
|||
dec j
|
||||
dec k
|
||||
|
||||
proc sort(a: var seq[(float32, int16)], inl, inr: int) =
|
||||
proc sort(a: var seq[(float32, int16)], inl, inr: int) {.raises: [].} =
|
||||
## Quicksort + insertion sort, in-place and faster than standard lib sort.
|
||||
let n = inr - inl + 1
|
||||
if n < 32:
|
||||
|
@ -1122,7 +1136,9 @@ proc sort(a: var seq[(float32, int16)], inl, inr: int) =
|
|||
sort(a, inl, r)
|
||||
sort(a, l, inr)
|
||||
|
||||
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
|
||||
proc shouldFill(
|
||||
windingRule: WindingRule, count: int
|
||||
): bool {.inline, raises: [].} =
|
||||
## Should we fill based on the current winding rule and count?
|
||||
case windingRule:
|
||||
of wrNonZero:
|
||||
|
@ -1136,7 +1152,7 @@ iterator walk(
|
|||
windingRule: WindingRule,
|
||||
y: int,
|
||||
size: Vec2
|
||||
): (float32, float32, int32) =
|
||||
): (float32, float32, int32) {.raises: [].} =
|
||||
var
|
||||
prevAt: float32
|
||||
count: int32
|
||||
|
@ -1168,7 +1184,7 @@ proc computeCoverages(
|
|||
aa: bool,
|
||||
partitioning: Partitioning,
|
||||
windingRule: WindingRule
|
||||
) {.inline.} =
|
||||
) {.inline, raises: [].} =
|
||||
let
|
||||
quality = if aa: 5 else: 1 # Must divide 255 cleanly (1, 3, 5, 15, 17, 51, 85)
|
||||
sampleCoverage = (255 div quality).uint8
|
||||
|
@ -1238,7 +1254,9 @@ proc computeCoverages(
|
|||
for j in i ..< fillStart + fillLen:
|
||||
coverages[j - startX] += sampleCoverage
|
||||
|
||||
proc clearUnsafe(target: Image | Mask, startX, startY, toX, toY: int) =
|
||||
proc clearUnsafe(
|
||||
target: Image | Mask, startX, startY, toX, toY: int
|
||||
) {.raises: [].} =
|
||||
## Clears data from [start, to).
|
||||
if startX == target.width or startY == target.height:
|
||||
return
|
||||
|
@ -1256,7 +1274,7 @@ proc fillCoverage(
|
|||
startX, y: int,
|
||||
coverages: seq[uint8],
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
var x = startX
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
if blendMode.hasSimdBlender():
|
||||
|
@ -1343,7 +1361,7 @@ proc fillCoverage(
|
|||
startX, y: int,
|
||||
coverages: seq[uint8],
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
var x = startX
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
if blendMode.hasSimdMasker():
|
||||
|
@ -1386,7 +1404,7 @@ proc fillHits(
|
|||
numHits: int,
|
||||
windingRule: WindingRule,
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
let blender = blendMode.blender()
|
||||
var filledTo: int
|
||||
for (prevAt, at, count) in hits.walk(numHits, windingRule, y, image.wh):
|
||||
|
@ -1434,7 +1452,7 @@ proc fillHits(
|
|||
numHits: int,
|
||||
windingRule: WindingRule,
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
let masker = blendMode.masker()
|
||||
var filledTo: int
|
||||
for (prevAt, at, count) in hits.walk(numHits, windingRule, y, mask.wh):
|
||||
|
@ -1478,7 +1496,7 @@ proc fillShapes(
|
|||
color: SomeColor,
|
||||
windingRule: WindingRule,
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
# Figure out the total bounds of all the shapes,
|
||||
# rasterize only within the total bounds
|
||||
let
|
||||
|
@ -1536,7 +1554,7 @@ proc fillShapes(
|
|||
shapes: seq[seq[Vec2]],
|
||||
windingRule: WindingRule,
|
||||
blendMode: BlendMode
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
# Figure out the total bounds of all the shapes,
|
||||
# rasterize only within the total bounds
|
||||
let
|
||||
|
@ -1574,11 +1592,11 @@ proc fillShapes(
|
|||
mask.clearUnsafe(0, 0, 0, startY)
|
||||
mask.clearUnsafe(0, pathHeight, 0, mask.height)
|
||||
|
||||
proc miterLimitToAngle*(limit: float32): float32 =
|
||||
proc miterLimitToAngle*(limit: float32): float32 {.inline, raises: [].} =
|
||||
## Converts miter-limit-ratio to miter-limit-angle.
|
||||
arcsin(1 / limit) * 2
|
||||
|
||||
proc angleToMiterLimit*(angle: float32): float32 =
|
||||
proc angleToMiterLimit*(angle: float32): float32 {.inline, raises: [].} =
|
||||
## Converts miter-limit-angle to miter-limit-ratio.
|
||||
1 / sin(angle / 2)
|
||||
|
||||
|
@ -1589,7 +1607,7 @@ proc strokeShapes(
|
|||
lineJoin: LineJoin,
|
||||
miterLimit: float32,
|
||||
dashes: seq[float32]
|
||||
): seq[seq[Vec2]] =
|
||||
): seq[seq[Vec2]] {.raises: [PixieError].} =
|
||||
if strokeWidth <= 0:
|
||||
return
|
||||
|
||||
|
@ -1725,7 +1743,7 @@ proc strokeShapes(
|
|||
|
||||
proc parseSomePath(
|
||||
path: SomePath, closeSubpaths: bool, pixelScale: float32 = 1.0
|
||||
): seq[seq[Vec2]] {.inline.} =
|
||||
): seq[seq[Vec2]] {.inline, raises: [PixieError].} =
|
||||
## Given SomePath, parse it in different ways.
|
||||
when type(path) is string:
|
||||
parsePath(path).commandsToShapes(closeSubpaths, pixelScale)
|
||||
|
@ -1738,7 +1756,7 @@ proc fillPath*(
|
|||
transform = mat3(),
|
||||
windingRule = wrNonZero,
|
||||
blendMode = bmNormal
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Fills a path.
|
||||
var shapes = parseSomePath(path, true, transform.pixelScale())
|
||||
shapes.transform(transform)
|
||||
|
@ -1750,7 +1768,7 @@ proc fillPath*(
|
|||
paint: Paint,
|
||||
transform = mat3(),
|
||||
windingRule = wrNonZero
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Fills a path.
|
||||
if paint.opacity == 0:
|
||||
return
|
||||
|
@ -1804,7 +1822,7 @@ proc strokePath*(
|
|||
miterLimit = defaultMiterLimit,
|
||||
dashes: seq[float32] = @[],
|
||||
blendMode = bmNormal
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Strokes a path.
|
||||
var strokeShapes = strokeShapes(
|
||||
parseSomePath(path, false, transform.pixelScale()),
|
||||
|
@ -1827,7 +1845,7 @@ proc strokePath*(
|
|||
lineJoin = ljMiter,
|
||||
miterLimit = defaultMiterLimit,
|
||||
dashes: seq[float32] = @[]
|
||||
) =
|
||||
) {.raises: [PixieError].} =
|
||||
## Strokes a path.
|
||||
if paint.opacity == 0:
|
||||
return
|
||||
|
@ -1890,7 +1908,7 @@ proc overlaps(
|
|||
shapes: seq[seq[Vec2]],
|
||||
test: Vec2,
|
||||
windingRule: WindingRule
|
||||
): bool =
|
||||
): bool {.raises: [].} =
|
||||
var hits: seq[(float32, int16)]
|
||||
|
||||
let
|
||||
|
@ -1920,7 +1938,7 @@ proc fillOverlaps*(
|
|||
test: Vec2,
|
||||
transform = mat3(), ## Applied to the path, not the test point.
|
||||
windingRule = wrNonZero
|
||||
): bool =
|
||||
): bool {.raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is contained in the current path.
|
||||
var shapes = parseSomePath(path, true, transform.pixelScale())
|
||||
shapes.transform(transform)
|
||||
|
@ -1935,7 +1953,7 @@ proc strokeOverlaps*(
|
|||
lineJoin = ljMiter,
|
||||
miterLimit = defaultMiterLimit,
|
||||
dashes: seq[float32] = @[],
|
||||
): bool =
|
||||
): bool {.raises: [PixieError].} =
|
||||
## Returns whether or not the specified point is inside the area contained
|
||||
## by the stroking of a path.
|
||||
var strokeShapes = strokeShapes(
|
||||
|
|
|
@ -154,7 +154,9 @@ block:
|
|||
let image = newImage(200, 100)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(font, "First line")
|
||||
image.fillText(font, "Second line", translate(vec2(0, font.defaultLineHeight)))
|
||||
image.fillText(
|
||||
font, "Second line", translate(vec2(0, font.defaultLineHeight))
|
||||
)
|
||||
|
||||
doDiff(image, "basic7")
|
||||
|
||||
|
|
Loading…
Reference in a new issue