decodeImageDimensions(data) and readImageDimensions(path)
This commit is contained in:
parent
a039c3681b
commit
5e6103fbc0
|
@ -93,6 +93,9 @@ exportObject ColorStop:
|
|||
exportObject TextMetrics:
|
||||
discard
|
||||
|
||||
exportObject ImageDimensions:
|
||||
discard
|
||||
|
||||
exportSeq seq[float32]:
|
||||
discard
|
||||
|
||||
|
@ -310,7 +313,10 @@ exportRefObject Context:
|
|||
isPointInStroke(Context, Path, float32, float32)
|
||||
|
||||
exportProcs:
|
||||
decodeImage
|
||||
decodeImageDimensions
|
||||
readImage
|
||||
readImageDimensions
|
||||
readmask
|
||||
readTypeface
|
||||
readFont
|
||||
|
|
|
@ -18,6 +18,25 @@ converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline, raises: [].
|
|||
## Convert a straight alpha RGBA to a premultiplied alpha RGBA.
|
||||
c.rgbx()
|
||||
|
||||
proc decodeImageDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes an image's dimensions from memory.
|
||||
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
|
||||
decodePngDimensions(data)
|
||||
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage):
|
||||
decodeJpegDimensions(data)
|
||||
elif data.len > 2 and data.readStr(0, 2) == bmpSignature:
|
||||
decodeBmpDimensions(data)
|
||||
elif data.len > 6 and data.readStr(0, 6) in gifSignatures:
|
||||
decodeGifDimensions(data)
|
||||
elif data.len > (14+8) and data.readStr(0, 4) == qoiSignature:
|
||||
decodeQoiDimensions(data)
|
||||
elif data.len > 9 and data.readStr(0, 2) in ppmSignatures:
|
||||
decodePpmDimensions(data)
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported image file format")
|
||||
|
||||
proc decodeImage*(data: string): Image {.raises: [PixieError].} =
|
||||
## Loads an image from memory.
|
||||
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
|
||||
|
@ -45,6 +64,15 @@ proc decodeMask*(data: string): Mask {.raises: [PixieError].} =
|
|||
else:
|
||||
raise newException(PixieError, "Unsupported mask file format")
|
||||
|
||||
proc readImageDimensions*(
|
||||
filePath: string
|
||||
): ImageDimensions {.inline, raises: [PixieError].} =
|
||||
## Loads an image from a file.
|
||||
try:
|
||||
decodeImageDimensions(readFile(filePath))
|
||||
except IOError as e:
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc readImage*(filePath: string): Image {.inline, raises: [PixieError].} =
|
||||
## Loads an image from a file.
|
||||
try:
|
||||
|
|
|
@ -28,6 +28,9 @@ type
|
|||
SubtractMaskBlend ## Inverse mask
|
||||
ExcludeMaskBlend
|
||||
|
||||
ImageDimensions* = object
|
||||
width*, height*: int
|
||||
|
||||
proc mix*(a, b: uint8, t: float32): uint8 {.inline, raises: [].} =
|
||||
## Linearly interpolate between a and b using t.
|
||||
let t = round(t * 255).uint32
|
||||
|
|
|
@ -227,6 +227,21 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
|
|||
|
||||
decodeDib(data[14].unsafeAddr, data.len - 14)
|
||||
|
||||
proc decodeBmpDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the BMP dimensions.
|
||||
|
||||
if data.len < 26:
|
||||
failInvalid()
|
||||
|
||||
# BMP Header
|
||||
if data[0 .. 1] != "BM":
|
||||
failInvalid()
|
||||
|
||||
result.width = data.readInt32(18).int
|
||||
result.height = abs(data.readInt32(22)).int
|
||||
|
||||
proc encodeBmp*(image: Image): string {.raises: [].} =
|
||||
## Encodes an image into the BMP file format.
|
||||
|
||||
|
|
|
@ -178,5 +178,18 @@ proc decodeGif*(data: string): Image {.raises: [PixieError].} =
|
|||
else:
|
||||
raise newException(PixieError, "Invalid GIF block type")
|
||||
|
||||
proc decodeGifDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the GIF dimensions.
|
||||
if data.len < 10:
|
||||
failInvalid()
|
||||
|
||||
if data[0 .. 5] notin gifSignatures:
|
||||
raise newException(PixieError, "Invalid GIF file signature")
|
||||
|
||||
result.width = data.readInt16(6).int
|
||||
result.height = data.readInt16(8).int
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -1056,7 +1056,7 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
|
|||
state.decodeDHT()
|
||||
of 0xD8:
|
||||
# SOI - Start of Image
|
||||
continue
|
||||
discard
|
||||
of 0xD9:
|
||||
# EOI - End of Image
|
||||
break
|
||||
|
@ -1094,5 +1094,63 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
|
|||
|
||||
state.buildImage()
|
||||
|
||||
proc decodeJpegDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the JPEG dimensions.
|
||||
|
||||
var state = DecoderState()
|
||||
state.buffer = cast[ptr UncheckedArray[uint8]](data.cstring)
|
||||
state.len = data.len
|
||||
|
||||
while true:
|
||||
if state.readUint8() != 0xFF:
|
||||
failInvalid("invalid chunk marker")
|
||||
|
||||
let chunkId = state.readUint8()
|
||||
case chunkId:
|
||||
of 0xD8:
|
||||
# SOI - Start of Image
|
||||
discard
|
||||
of 0xC0:
|
||||
# Start Of Frame (Baseline DCT)
|
||||
state.decodeSOF0()
|
||||
break
|
||||
of 0xC1:
|
||||
# Start Of Frame (Extended sequential DCT)
|
||||
state.decodeSOF1()
|
||||
break
|
||||
of 0xC2:
|
||||
# Start Of Frame (Progressive DCT)
|
||||
state.decodeSOF2()
|
||||
break
|
||||
of 0xDB:
|
||||
# Define Quantization Table(s)
|
||||
state.skipChunk()
|
||||
of 0XE0:
|
||||
# Application-specific
|
||||
state.skipChunk()
|
||||
of 0xE1:
|
||||
# Exif/APP1
|
||||
state.decodeExif()
|
||||
of 0xE2..0xEF:
|
||||
# Application-specific
|
||||
state.skipChunk()
|
||||
of 0xFE:
|
||||
# Comment
|
||||
state.skipChunk()
|
||||
else:
|
||||
failInvalid("invalid chunk " & chunkId.toHex())
|
||||
|
||||
case state.orientation:
|
||||
of 0, 1, 2, 3, 4:
|
||||
result.width = state.imageWidth
|
||||
result.height = state.imageHeight
|
||||
of 5, 6, 7, 8:
|
||||
result.width = state.imageHeight
|
||||
result.height = state.imageWidth
|
||||
else:
|
||||
failInvalid("invalid orientation")
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -341,6 +341,26 @@ proc newImage*(png: Png): Image {.raises: [PixieError].} =
|
|||
copyMem(result.data[0].addr, png.data[0].addr, png.data.len * 4)
|
||||
result.data.toPremultipliedAlpha()
|
||||
|
||||
proc decodePngDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the PNG dimensions.
|
||||
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
|
||||
failInvalid()
|
||||
|
||||
# PNG file signature
|
||||
let signature = cast[array[8, uint8]](data.readUint64(0))
|
||||
if signature != pngSignature:
|
||||
failInvalid()
|
||||
|
||||
# First chunk must be IHDR
|
||||
if data.readUint32(8).swap() != 13 or data.readStr(12, 4) != "IHDR":
|
||||
failInvalid()
|
||||
|
||||
let header = decodeHeader(data[16 ..< 16 + 13])
|
||||
result.width = header.width
|
||||
result.height = header.height
|
||||
|
||||
proc decodePng*(data: string): Png {.raises: [PixieError].} =
|
||||
## Decodes the PNG data.
|
||||
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
|
||||
|
|
|
@ -135,6 +135,14 @@ proc decodePpm*(data: string): Image {.raises: [PixieError].} =
|
|||
else:
|
||||
decodeP6Data(data[header.dataOffset .. ^1], header.maxVal)
|
||||
|
||||
proc decodePpmDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the PPM dimensions.
|
||||
let header = decodeHeader(data)
|
||||
result.width = header.width
|
||||
result.height = header.height
|
||||
|
||||
proc encodePpm*(image: Image): string {.raises: [].} =
|
||||
## Encodes an image into the PPM file format (version P6).
|
||||
|
||||
|
|
|
@ -121,6 +121,16 @@ proc decodeQoi*(data: string): Qoi {.raises: [PixieError].} =
|
|||
raise newException(PixieError, "Invalid QOI padding")
|
||||
inc(p)
|
||||
|
||||
proc decodeQoiDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the QOI dimensions.
|
||||
if data.len <= 12 or data[0 .. 3] != qoiSignature:
|
||||
raise newException(PixieError, "Invalid QOI header")
|
||||
|
||||
result.width = data.readUint32(4).swap().int
|
||||
result.height = data.readUint32(8).swap().int
|
||||
|
||||
proc encodeQoi*(qoi: Qoi): string {.raises: [PixieError].} =
|
||||
## Encodes raw QOI pixels to the QOI file format.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import chroma, os, pixie, pixie/fileformats/bmp, strutils
|
||||
import os, pixie/fileformats/bmp
|
||||
|
||||
# block:
|
||||
# var image = newImage(4, 2)
|
||||
|
@ -32,9 +32,9 @@ import chroma, os, pixie, pixie/fileformats/bmp, strutils
|
|||
|
||||
block:
|
||||
for bits in [32, 24]:
|
||||
let image = decodeBmp(readFile(
|
||||
"tests/fileformats/bmp/knight." & $bits & ".master.bmp"
|
||||
))
|
||||
let
|
||||
path = "tests/fileformats/bmp/knight." & $bits & ".master.bmp"
|
||||
image = decodeBmp(readFile(path))
|
||||
writeFile("tests/fileformats/bmp/knight." & $bits & ".bmp", encodeBmp(image))
|
||||
|
||||
block:
|
||||
|
@ -46,5 +46,9 @@ block:
|
|||
block:
|
||||
for file in walkFiles("tests/fileformats/bmp/bmpsuite/*"):
|
||||
# echo file
|
||||
let image = decodeBmp(readFile(file))
|
||||
let
|
||||
image = decodeBmp(readFile(file))
|
||||
dimensions = decodeBmpDimensions(readFile(file))
|
||||
#image.writeFile(file.replace("bmpsuite", "output") & ".png")
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
|
|
@ -1,13 +1,37 @@
|
|||
import pixie, pixie/fileformats/gif
|
||||
|
||||
var img = decodeGIF(readFile("tests/fileformats/gif/3x5.gif"))
|
||||
img.writeFile("tests/fileformats/gif/3x5.png")
|
||||
block:
|
||||
let
|
||||
path = "tests/fileformats/gif/3x5.gif"
|
||||
image = decodeGIF(readFile(path))
|
||||
dimensions = decodeGifDimensions(readFile(path))
|
||||
image.writeFile("tests/fileformats/gif/3x5.png")
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
||||
var img2 = decodeGIF(readFile("tests/fileformats/gif/audrey.gif"))
|
||||
img2.writeFile("tests/fileformats/gif/audrey.png")
|
||||
block:
|
||||
let
|
||||
path = "tests/fileformats/gif/audrey.gif"
|
||||
image = decodeGIF(readFile(path))
|
||||
dimensions = decodeGifDimensions(readFile(path))
|
||||
image.writeFile("tests/fileformats/gif/audrey.png")
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
||||
var img3 = decodeGIF(readFile("tests/fileformats/gif/sunflower.gif"))
|
||||
img3.writeFile("tests/fileformats/gif/sunflower.png")
|
||||
block:
|
||||
let
|
||||
path = "tests/fileformats/gif/sunflower.gif"
|
||||
image = decodeGIF(readFile(path))
|
||||
dimensions = decodeGifDimensions(readFile(path))
|
||||
image.writeFile("tests/fileformats/gif/sunflower.png")
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
||||
var img4 = readImage("tests/fileformats/gif/sunflower.gif")
|
||||
doAssert img3.data == img4.data
|
||||
block:
|
||||
let
|
||||
path = "tests/fileformats/gif/sunflower.gif"
|
||||
image = decodeGIF(readFile(path))
|
||||
dimensions = decodeGifDimensions(readFile(path))
|
||||
image.writeFile("tests/fileformats/gif/sunflower.png")
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import jpegsuite, pixie
|
||||
import jpegsuite, pixie, pixie/fileformats/jpeg
|
||||
|
||||
for file in jpegSuiteFiles:
|
||||
let img = readImage(file)
|
||||
let
|
||||
image = readImage(file)
|
||||
dimensions = decodeJpegDimensions(readFile(file))
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
|
|
@ -33,3 +33,9 @@ block:
|
|||
|
||||
block:
|
||||
discard readImage("tests/fileformats/png/trailing_data.png")
|
||||
|
||||
block:
|
||||
let dimensions =
|
||||
decodeImageDimensions(readFile("tests/fileformats/png/mandrill.png"))
|
||||
doAssert dimensions.width == 512
|
||||
doAssert dimensions.height == 512
|
||||
|
|
|
@ -2,13 +2,22 @@ import pixie/fileformats/ppm
|
|||
|
||||
block:
|
||||
for format in @["p3", "p6"]:
|
||||
let image = decodePpm(readFile(
|
||||
"tests/fileformats/ppm/feep." & $format & ".master.ppm"
|
||||
))
|
||||
let
|
||||
path = "tests/fileformats/ppm/feep." & $format & ".master.ppm"
|
||||
image = decodePpm(readFile(path))
|
||||
dimensions = decodePpmDimensions(readFile(path))
|
||||
writeFile("tests/fileformats/ppm/feep." & $format & ".ppm", encodePpm(image))
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
||||
let image = decodePpm(readFile("tests/fileformats/ppm/feep.p3.hidepth.master.ppm"))
|
||||
block:
|
||||
let
|
||||
path = "tests/fileformats/ppm/feep.p3.hidepth.master.ppm"
|
||||
image = decodePpm(readFile(path))
|
||||
dimensions = decodePpmDimensions(readFile(path))
|
||||
writeFile("tests/fileformats/ppm/feep.p3.hidepth.ppm", encodePpm(image))
|
||||
doAssert image.width == dimensions.width
|
||||
doAssert image.height == dimensions.height
|
||||
|
||||
# produced output should be identical to P6 master
|
||||
let p6Master = readFile("tests/fileformats/ppm/feep.p6.master.ppm")
|
||||
|
|
|
@ -4,14 +4,19 @@ const tests = ["testcard", "testcard_rgba"]
|
|||
|
||||
for name in tests:
|
||||
let
|
||||
input = readImage("tests/fileformats/qoi/" & name & ".qoi")
|
||||
path = "tests/fileformats/qoi/" & name & ".qoi"
|
||||
input = readImage(path)
|
||||
control = readImage("tests/fileformats/qoi/" & name & ".png")
|
||||
dimensions = decodeQoiDimensions(readFile(path))
|
||||
doAssert input.data == control.data, "input mismatch of " & name
|
||||
doAssert input.width == dimensions.width
|
||||
doAssert input.height == dimensions.height
|
||||
discard encodeQoi(control)
|
||||
|
||||
for name in tests:
|
||||
let
|
||||
input = decodeQoi(readFile("tests/fileformats/qoi/" & name & ".qoi"))
|
||||
path = "tests/fileformats/qoi/" & name & ".qoi"
|
||||
input = decodeQoi(readFile(path))
|
||||
output = decodeQoi(encodeQoi(input))
|
||||
doAssert output.data.len == input.data.len
|
||||
doAssert output.data == input.data
|
||||
|
|
Loading…
Reference in a new issue