Merge pull request #541 from treeform/guzba
improved image dimension proc functionality
This commit is contained in:
commit
523b364fca
|
@ -18,24 +18,36 @@ converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline, raises: [].
|
|||
## Convert a straight alpha RGBA to a premultiplied alpha RGBA.
|
||||
c.rgbx()
|
||||
|
||||
proc decodeImageDimensions*(
|
||||
data: pointer, len: int
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes an image's dimensions from memory.
|
||||
if len > 8 and equalMem(data, pngSignature[0].unsafeAddr, 8):
|
||||
decodePngDimensions(data, len)
|
||||
elif len > 2 and equalMem(data, jpegStartOfImage[0].unsafeAddr, 2):
|
||||
decodeJpegDimensions(data, len)
|
||||
elif len > 2 and equalMem(data, bmpSignature.cstring, 2):
|
||||
decodeBmpDimensions(data, len)
|
||||
elif len > 6 and (
|
||||
equalMem(data, gifSignatures[0].cstring, 6) or
|
||||
equalMem(data, gifSignatures[1].cstring, 6)
|
||||
):
|
||||
decodeGifDimensions(data, len)
|
||||
elif len > (14 + 8) and equalMem(data, qoiSignature.cstring, 4):
|
||||
decodeQoiDimensions(data, len)
|
||||
elif len > 9 and (
|
||||
equalMem(data, ppmSignatures[0].cstring, 2) or
|
||||
equalMem(data, ppmSignatures[1].cstring, 2)
|
||||
):
|
||||
decodePpmDimensions(data, len)
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported image file format")
|
||||
|
||||
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")
|
||||
decodeImageDimensions(data.cstring, data.len)
|
||||
|
||||
proc decodeImage*(data: string): Image {.raises: [PixieError].} =
|
||||
## Loads an image from memory.
|
||||
|
|
|
@ -228,19 +228,27 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
|
|||
decodeDib(data[14].unsafeAddr, data.len - 14)
|
||||
|
||||
proc decodeBmpDimensions*(
|
||||
data: string
|
||||
data: pointer, len: int
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the BMP dimensions.
|
||||
if data.len < 26:
|
||||
if len < 26:
|
||||
failInvalid()
|
||||
|
||||
let data = cast[ptr UncheckedArray[uint8]](data)
|
||||
|
||||
# BMP Header
|
||||
if data[0 .. 1] != "BM":
|
||||
if data[0].char != 'B' or data[1].char != 'M': # Must start with BM
|
||||
failInvalid()
|
||||
|
||||
result.width = data.readInt32(18).int
|
||||
result.height = abs(data.readInt32(22)).int
|
||||
|
||||
proc decodeBmpDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the BMP dimensions.
|
||||
decodeBmpDimensions(data.cstring, data.len)
|
||||
|
||||
proc encodeDib*(image: Image): string {.raises: [].} =
|
||||
## Encodes an image into a DIB.
|
||||
|
||||
|
|
|
@ -379,18 +379,29 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
|
|||
result.duration += interval
|
||||
|
||||
proc decodeGifDimensions*(
|
||||
data: string
|
||||
data: pointer, len: int
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the GIF dimensions.
|
||||
if data.len < 10:
|
||||
if len < 10:
|
||||
failInvalid()
|
||||
|
||||
if data[0 .. 5] notin gifSignatures:
|
||||
let data = cast[ptr UncheckedArray[uint8]](data)
|
||||
|
||||
let startsWithSignature =
|
||||
equalMem(data, gifSignatures[0].cstring, 6) or
|
||||
equalMem(data, gifSignatures[1].cstring, 6)
|
||||
|
||||
if not startsWithSignature:
|
||||
raise newException(PixieError, "Invalid GIF file signature")
|
||||
|
||||
result.width = data.readInt16(6).int
|
||||
result.height = data.readInt16(8).int
|
||||
|
||||
proc decodeGifDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
decodeGifDimensions(data.cstring, data.len)
|
||||
|
||||
proc newImage*(gif: Gif): Image {.raises: [].} =
|
||||
gif.frames[0].copy()
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import chroma, flatty/binny, ../common, ../images, ../internal,
|
|||
|
||||
const
|
||||
fastBits = 9
|
||||
jpegStartOfImage* = [0xFF.uint8, 0xD8]
|
||||
deZigZag = [
|
||||
uint8 00, 01, 08, 16, 09, 02, 03, 10,
|
||||
uint8 17, 24, 32, 25, 18, 11, 04, 05,
|
||||
|
@ -40,6 +39,9 @@ const
|
|||
1023, 2047, 4095, 8191, 16383, 32767, 65535
|
||||
]
|
||||
|
||||
let
|
||||
jpegStartOfImage* = [0xFF.uint8, 0xD8]
|
||||
|
||||
type
|
||||
Huffman = object
|
||||
codes: array[256, uint16]
|
||||
|
@ -380,15 +382,16 @@ proc decodeSOF2(state: var DecoderState) =
|
|||
|
||||
proc decodeExif(state: var DecoderState) =
|
||||
## Decode Exif header
|
||||
let
|
||||
len = state.readUint16be().int - 2
|
||||
endOffset = state.pos + len
|
||||
var len = state.readUint16be().int - 2
|
||||
|
||||
let exifHeader = state.readStr(6)
|
||||
|
||||
len -= 6
|
||||
|
||||
if exifHeader != "Exif\0\0":
|
||||
# Happens with progressive images, just ignore instead of error.
|
||||
# Skip to the end.
|
||||
state.pos = endOffset
|
||||
state.skipBytes(len)
|
||||
return
|
||||
|
||||
# Read the endianess of the exif header
|
||||
|
@ -402,22 +405,40 @@ proc decodeExif(state: var DecoderState) =
|
|||
else:
|
||||
failInvalid("invalid Tiff header")
|
||||
|
||||
len -= 2
|
||||
|
||||
# Verify we got the endianess right.
|
||||
if state.readUint16be().maybeSwap(littleEndian) != 0x002A.uint16:
|
||||
failInvalid("invalid Tiff header endianess")
|
||||
|
||||
len -= 2
|
||||
|
||||
# Skip any other tiff header data.
|
||||
let offsetToFirstIFD = state.readUint32be().maybeSwap(littleEndian).int
|
||||
|
||||
len -= 4
|
||||
|
||||
if offsetToFirstIFD < 8:
|
||||
failInvalid("invalid Tiff offset")
|
||||
|
||||
state.skipBytes(offsetToFirstIFD - 8)
|
||||
|
||||
len -= (offsetToFirstIFD - 8)
|
||||
|
||||
# Read the IFD0 (main image) tags.
|
||||
let numTags = state.readUint16be().maybeSwap(littleEndian).int
|
||||
|
||||
len -= 2
|
||||
|
||||
for i in 0 ..< numTags:
|
||||
let
|
||||
tagNumber = state.readUint16be().maybeSwap(littleEndian)
|
||||
dataFormat = state.readUint16be().maybeSwap(littleEndian)
|
||||
numberComponents = state.readUint32be().maybeSwap(littleEndian)
|
||||
dataOffset = state.readUint32be().maybeSwap(littleEndian).int
|
||||
|
||||
len -= 12
|
||||
|
||||
# For now we only care about orientation tag.
|
||||
case tagNumber:
|
||||
of 0x0112: # Orientation
|
||||
|
@ -426,7 +447,7 @@ proc decodeExif(state: var DecoderState) =
|
|||
discard
|
||||
|
||||
# Skip all of the data we do not want to read, IFD1, thumbnail, etc.
|
||||
state.pos = endOffset
|
||||
state.skipBytes(len) # Skip any remaining len
|
||||
|
||||
proc reset(state: var DecoderState) =
|
||||
## Rests the decoder state need for restart markers.
|
||||
|
@ -1136,13 +1157,13 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
|
|||
state.buildImage()
|
||||
|
||||
proc decodeJpegDimensions*(
|
||||
data: string
|
||||
data: pointer, len: int
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the JPEG dimensions.
|
||||
|
||||
var state = DecoderState()
|
||||
state.buffer = cast[ptr UncheckedArray[uint8]](data.cstring)
|
||||
state.len = data.len
|
||||
state.buffer = cast[ptr UncheckedArray[uint8]](data)
|
||||
state.len = len
|
||||
|
||||
while true:
|
||||
if state.readUint8() != 0xFF:
|
||||
|
@ -1153,21 +1174,24 @@ proc decodeJpegDimensions*(
|
|||
of 0xD8:
|
||||
# SOI - Start of Image
|
||||
discard
|
||||
of 0xC0:
|
||||
# Start Of Frame (Baseline DCT)
|
||||
state.decodeSOF0()
|
||||
of 0xC0, 0xC2:
|
||||
# Start Of Frame (Baseline DCT or Progressive DCT)
|
||||
discard state.readUint16be().int # Chunk len
|
||||
discard state.readUint8() # Precision
|
||||
state.imageHeight = state.readUint16be().int
|
||||
state.imageWidth = state.readUint16be().int
|
||||
break
|
||||
of 0xC1:
|
||||
# Start Of Frame (Extended sequential DCT)
|
||||
state.decodeSOF1()
|
||||
break
|
||||
of 0xC2:
|
||||
# Start Of Frame (Progressive DCT)
|
||||
state.decodeSOF2()
|
||||
break
|
||||
failInvalid("unsupported extended sequential DCT format")
|
||||
of 0xC4:
|
||||
# Define Huffman Table
|
||||
state.decodeDHT()
|
||||
of 0xDB:
|
||||
# Define Quantization Table(s)
|
||||
state.skipChunk()
|
||||
of 0xDD:
|
||||
# Define Restart Interval
|
||||
state.skipChunk()
|
||||
of 0XE0:
|
||||
# Application-specific
|
||||
state.skipChunk()
|
||||
|
@ -1193,5 +1217,11 @@ proc decodeJpegDimensions*(
|
|||
else:
|
||||
failInvalid("invalid orientation")
|
||||
|
||||
proc decodeJpegDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the JPEG dimensions.
|
||||
decodeJpegDimensions(data.cstring, data.len)
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -3,7 +3,7 @@ import chroma, flatty/binny, math, ../common, ../images, ../internal,
|
|||
|
||||
# See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
|
||||
|
||||
const
|
||||
let
|
||||
pngSignature* = [137.uint8, 80, 78, 71, 13, 10, 26, 10]
|
||||
|
||||
type
|
||||
|
@ -76,14 +76,6 @@ proc decodeHeader(data: pointer): PngHeader =
|
|||
if result.interlaceMethod notin [0.uint8, 1]:
|
||||
raise newException(PixieError, "Invalid PNG interlace method")
|
||||
|
||||
# Not yet supported:
|
||||
|
||||
if result.bitDepth == 16:
|
||||
raise newException(PixieError, "PNG 16 bit depth not supported yet")
|
||||
|
||||
if result.interlaceMethod != 0:
|
||||
raise newException(PixieError, "Interlaced PNG not supported yet")
|
||||
|
||||
proc decodePalette(data: pointer, len: int): seq[ColorRGB] =
|
||||
if len == 0 or len mod 3 != 0:
|
||||
failInvalid()
|
||||
|
@ -451,6 +443,12 @@ proc decodePng*(data: pointer, len: int): Png {.raises: [PixieError].} =
|
|||
failCRC()
|
||||
inc(pos, 4) # CRC
|
||||
|
||||
# Not yet supported:
|
||||
if header.bitDepth == 16:
|
||||
raise newException(PixieError, "PNG 16 bit depth not supported yet")
|
||||
if header.interlaceMethod != 0:
|
||||
raise newException(PixieError, "Interlaced PNG not supported yet")
|
||||
|
||||
while true:
|
||||
if pos + 8 > len:
|
||||
failInvalid()
|
||||
|
|
|
@ -12,16 +12,17 @@ type
|
|||
template failInvalid() =
|
||||
raise newException(PixieError, "Invalid PPM data")
|
||||
|
||||
proc decodeHeader(data: string): PpmHeader {.raises: [PixieError].} =
|
||||
if data.len <= 10: # Each part + whitespace
|
||||
raise newException(PixieError, "Invalid PPM file header")
|
||||
|
||||
proc decodeHeader(
|
||||
data: ptr UncheckedArray[uint8], len: int
|
||||
): PpmHeader {.raises: [PixieError].} =
|
||||
var
|
||||
commentMode, readWhitespace: bool
|
||||
i, readFields: int
|
||||
field: string
|
||||
while readFields < 4:
|
||||
let c = readUint8(data, i).char
|
||||
if i >= len:
|
||||
raise newException(PixieError, "Invalid PPM file header")
|
||||
let c = data[i].char
|
||||
if c == '#':
|
||||
commentMode = true
|
||||
elif c == '\n':
|
||||
|
@ -120,7 +121,10 @@ proc decodeP3Data(data: string, maxVal: int): seq[ColorRGBX] {.raises: [PixieErr
|
|||
proc decodePpm*(data: string): Image {.raises: [PixieError].} =
|
||||
## Decodes Portable Pixel Map data into an Image.
|
||||
|
||||
let header = decodeHeader(data)
|
||||
let header = decodeHeader(
|
||||
cast[ptr UncheckedArray[uint8]](data.cstring),
|
||||
data.len
|
||||
)
|
||||
|
||||
if not (header.version in ppmSignatures):
|
||||
failInvalid()
|
||||
|
@ -135,13 +139,21 @@ proc decodePpm*(data: string): Image {.raises: [PixieError].} =
|
|||
else:
|
||||
decodeP6Data(data[header.dataOffset .. ^1], header.maxVal)
|
||||
|
||||
proc decodePpmDimensions*(
|
||||
data: pointer, len: int
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the PPM dimensions.
|
||||
let
|
||||
data = cast[ptr UncheckedArray[uint8]](data)
|
||||
header = decodeHeader(data, len)
|
||||
result.width = header.width
|
||||
result.height = header.height
|
||||
|
||||
proc decodePpmDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the PPM dimensions.
|
||||
let header = decodeHeader(data)
|
||||
result.width = header.width
|
||||
result.height = header.height
|
||||
decodePpmDimensions(data.cstring, data.len)
|
||||
|
||||
proc encodePpm*(image: Image): string {.raises: [].} =
|
||||
## Encodes an image into the PPM file format (version P6).
|
||||
|
|
|
@ -136,15 +136,23 @@ proc decodeQoi*(data: string): Qoi {.raises: [PixieError].} =
|
|||
inc(p)
|
||||
|
||||
proc decodeQoiDimensions*(
|
||||
data: string
|
||||
data: pointer, len: int
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the QOI dimensions.
|
||||
if data.len <= 12 or data[0 .. 3] != qoiSignature:
|
||||
if len <= 12 or not equalMem(data, qoiSignature.cstring, 4):
|
||||
raise newException(PixieError, "Invalid QOI header")
|
||||
|
||||
let data = cast[ptr UncheckedArray[uint8]](data)
|
||||
|
||||
result.width = data.readUint32(4).swap().int
|
||||
result.height = data.readUint32(8).swap().int
|
||||
|
||||
proc decodeQoiDimensions*(
|
||||
data: string
|
||||
): ImageDimensions {.raises: [PixieError].} =
|
||||
## Decodes the QOI dimensions.
|
||||
decodeQoiDimensions(data.cstring, data.len)
|
||||
|
||||
proc encodeQoi*(qoi: Qoi): string {.raises: [PixieError].} =
|
||||
## Encodes raw QOI pixels to the QOI file format.
|
||||
|
||||
|
|
Loading…
Reference in a new issue