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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bindings/bindings.nim
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ exportRefObject Image:
newImage(int, int)
procs:
writeFile(Image, string)
encodeBase64
copy(Image)
getColor
setColor
Expand Down Expand Up @@ -291,6 +292,7 @@ exportRefObject Context:
isPointInStroke(Context, Path, float32, float32)

exportProcs:
decodeBase64
decodeImage
decodeImageDimensions
readImage
Expand Down
17 changes: 10 additions & 7 deletions src/pixie.nim
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import bumpy, chroma, flatty/binny, os, pixie/common, pixie/contexts,
pixie/fileformats/bmp, pixie/fileformats/gif, pixie/fileformats/jpeg,
pixie/fileformats/png, pixie/fileformats/ppm, pixie/fileformats/qoi,
pixie/fileformats/svg, pixie/fonts, pixie/images, pixie/internal,
pixie/paints, pixie/paths, strutils, vmath
import
std/[os, strutils],
bumpy, chroma, flatty/binny, vmath,
pixie/[common, contexts, fonts, imagebase64, images, internal, paints, paths],
pixie/fileformats/[bmp, gif, jpeg, png, ppm, qoi, svg]

export bumpy, chroma, common, contexts, fonts, images, paints, paths, vmath
export bumpy, chroma, common, contexts, fonts, imagebase64, images, paints,
paths, vmath

type
FileFormat* = enum
Expand Down Expand Up @@ -85,7 +86,9 @@ proc readImage*(filePath: string): Image {.inline, raises: [PixieError].} =
except IOError as e:
raise newException(PixieError, e.msg, e)

proc encodeImage*(image: Image, fileFormat: FileFormat): string {.raises: [PixieError].} =
proc encodeImage*(
image: Image, fileFormat: FileFormat
): string {.raises: [PixieError].} =
## Encodes an image into memory.
case fileFormat:
of PngFormat:
Expand Down
52 changes: 52 additions & 0 deletions src/pixie/imagebase64.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import
std/base64 as stdbase64,
std/strutils,
flatty/binny,
common,
fileformats/[bmp, gif, jpeg, png, ppm, qoi, svg]

proc decodeImageData(data: string): Image {.raises: [PixieError].} =
## Decodes supported image bytes into an image.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
decodePng(data).convertToImage()
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage):
decodeJpeg(data)
elif data.len > 2 and data.readStr(0, 2) == bmpSignature:
decodeBmp(data)
elif data.len > 5 and (
data.readStr(0, 5) == xmlSignature or
data.readStr(0, 4) == svgSignature
):
newImage(parseSvg(data))
elif data.len > 6 and data.readStr(0, 6) in gifSignatures:
newImage(decodeGif(data))
elif data.len > (14 + 8) and data.readStr(0, 4) == qoiSignature:
decodeQoi(data).convertToImage()
elif data.len > 9 and data.readStr(0, 2) in ppmSignatures:
decodePpm(data)
else:
raise newException(PixieError, "Unsupported image file format")

proc getPayload(data: string): string {.raises: [PixieError].} =
## Gets the base64 payload from plain base64 data or a data URL.
if data.startsWith("data:"):
let comma = data.find(',')
if comma == -1:
raise newException(PixieError, "Invalid data URL")
if comma + 1 < data.len:
data[comma + 1 .. ^1]
else:
""
else:
data

proc encodeBase64*(image: Image): string {.raises: [PixieError].} =
## Encodes an image into PNG format and returns it as base64.
stdbase64.encode(image.encodePng())

proc decodeBase64*(data: string): Image {.raises: [PixieError].} =
## Decodes a base64 image string into an image.
try:
decodeImageData(stdbase64.decode(data.getPayload()))
except ValueError as e:
raise newException(PixieError, e.msg, e)
35 changes: 35 additions & 0 deletions tests/test_base64.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import
std/base64,
chroma, pixie, pixie/fileformats/png

block:
let image = newImage(2, 1)
image[0, 0] = rgba(255, 0, 0, 255)
image[1, 0] = rgba(0, 0, 255, 128)

let encoded = image.encodeBase64()
doAssert encoded == base64.encode(image.encodePng())

let decoded = decodeBase64(encoded)
doAssert decoded.width == image.width
doAssert decoded.height == image.height
doAssert decoded.data == image.data

block:
let image = newImage(1, 1)
image[0, 0] = rgba(0, 255, 0, 255)

let
encoded = image.encodeBase64()
decoded = decodeBase64("data:image/png;base64," & encoded)

doAssert decoded.width == image.width
doAssert decoded.height == image.height
doAssert decoded.data == image.data

block:
try:
discard decodeBase64("nope")
doAssert false
except PixieError:
discard
1 change: 1 addition & 0 deletions tests/tests.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import
test_base64,
test_bmp,
test_contexts,
test_fonts,
Expand Down