Started work on gif image loader.
This commit is contained in:
parent
fcd8d0aa4b
commit
e0ebc1c655
8 changed files with 200 additions and 0 deletions
159
src/pixie/fileformats/gif.nim
Normal file
159
src/pixie/fileformats/gif.nim
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import chroma, flatty/binny, pixie/common, pixie/images, math, bitty
|
||||||
|
|
||||||
|
const gifSignatures* = @["GIF87a", "GIF89a"]
|
||||||
|
|
||||||
|
# See: https://en.wikipedia.org/wiki/GIF
|
||||||
|
|
||||||
|
proc decodeGIF*(data: string): Image =
|
||||||
|
## Decodes GIF data into an Image.
|
||||||
|
|
||||||
|
let version = data[0 .. 5]
|
||||||
|
if version notin gifSignatures:
|
||||||
|
raise newException(PixieError, "Invalid GIF file signature.")
|
||||||
|
|
||||||
|
let
|
||||||
|
# Read information about the image.
|
||||||
|
width = data.readInt16(6).int
|
||||||
|
height = data.readInt16(8).int
|
||||||
|
flags = data.readUint8(10).int
|
||||||
|
hasColorTable = (flags and 0x80) != 0
|
||||||
|
originalDepth = ((flags and 0x70) shr 4) + 1
|
||||||
|
colorTableSorted = (flags and 0x8) != 0
|
||||||
|
colorTableSize = 2 ^ ((flags and 0x7) + 1)
|
||||||
|
bgColorIndex = data.readUint8(11)
|
||||||
|
pixelAspectRatio = data.readUint8(11)
|
||||||
|
|
||||||
|
result = newImage(width, height)
|
||||||
|
|
||||||
|
# Read the main color table.
|
||||||
|
var colors: seq[ColorRGBA]
|
||||||
|
var i = 0xD
|
||||||
|
if hasColorTable:
|
||||||
|
for c in 0 ..< colorTableSize:
|
||||||
|
let
|
||||||
|
r = data.readUint8(i + 0)
|
||||||
|
g = data.readUint8(i + 1)
|
||||||
|
b = data.readUint8(i + 2)
|
||||||
|
colors.add(rgba(r, g, b, 255))
|
||||||
|
i += 3
|
||||||
|
|
||||||
|
# Read the image blocks.
|
||||||
|
while true:
|
||||||
|
let blockType = data.readUint8(i)
|
||||||
|
i += 1
|
||||||
|
case blockType:
|
||||||
|
of 0x2c: # IMAGE
|
||||||
|
let
|
||||||
|
left = data.readUint16(i + 0)
|
||||||
|
top = data.readUint16(i + 2)
|
||||||
|
width = data.readUint16(i + 4)
|
||||||
|
height = data.readUint16(i + 6)
|
||||||
|
flags = data.readUint8(i + 8)
|
||||||
|
|
||||||
|
hasColorTable = (flags and 0x80) != 0
|
||||||
|
interlace = (flags and 0x40) != 0
|
||||||
|
colorTableSorted = (flags and 0x8) != 0
|
||||||
|
colorTableSize = 2 ^ ((flags and 0x7) + 1)
|
||||||
|
|
||||||
|
i += 9
|
||||||
|
|
||||||
|
# Make sure we support the GIF features.
|
||||||
|
if left != 0 and top != 0 and
|
||||||
|
width.int != result.width and height.int != result.height:
|
||||||
|
raise newException(PixieError, "Image block offsets not supported.")
|
||||||
|
|
||||||
|
if hasColorTable:
|
||||||
|
raise newException(PixieError, "Color table per block not supported.")
|
||||||
|
|
||||||
|
if interlace:
|
||||||
|
raise newException(PixieError, "Interlacing not supported.")
|
||||||
|
|
||||||
|
# Read the lzw data chunks.
|
||||||
|
let lzwMinBitSize = data.readUint8(i)
|
||||||
|
i += 1
|
||||||
|
var lzwData = ""
|
||||||
|
while true:
|
||||||
|
let lzwEncodedLen = data.readUint8(i)
|
||||||
|
i += 1
|
||||||
|
if lzwEncodedLen == 0:
|
||||||
|
# Stop reading when chunk len is 0.
|
||||||
|
break
|
||||||
|
lzwData.add data[i ..< i + lzwEncodedLen.int]
|
||||||
|
i += lzwEncodedLen.int
|
||||||
|
|
||||||
|
let
|
||||||
|
clearMark = 1 shl lzwMinBitSize
|
||||||
|
endMark = clearMark + 1
|
||||||
|
|
||||||
|
# Turn full lzw data into bit stream.
|
||||||
|
var
|
||||||
|
bs = newBitStream(lzwData)
|
||||||
|
bitSize = lzwMinBitSize + 1
|
||||||
|
currentCodeTableMax = (1 shl (bitSize)) - 1
|
||||||
|
codeLast: int = -1
|
||||||
|
codeTable: seq[seq[int]]
|
||||||
|
colorIndexes: seq[int]
|
||||||
|
|
||||||
|
# Main decode loop.
|
||||||
|
while codeLast != endMark:
|
||||||
|
|
||||||
|
var
|
||||||
|
# Read variable bits out of the table.
|
||||||
|
codeId = bs.read(bitSize.int)
|
||||||
|
# Some time we need to carry over table information.
|
||||||
|
carryOver: seq[int]
|
||||||
|
|
||||||
|
if codeId == clearMark:
|
||||||
|
# Clear and re-init the tables.
|
||||||
|
bitSize = lzwMinBitSize + 1
|
||||||
|
currentCodeTableMax = (1 shl (bitSize)) - 1
|
||||||
|
codeLast = -1
|
||||||
|
codeTable.setLen(0)
|
||||||
|
for x in 0 ..< endMark + 1:
|
||||||
|
codeTable.add(@[x])
|
||||||
|
|
||||||
|
elif codeId == endMark:
|
||||||
|
# Exit we are done.
|
||||||
|
break
|
||||||
|
|
||||||
|
elif codeId < codeTable.len and codeTable[codeId].len > 0:
|
||||||
|
# Its in the current table, use it.
|
||||||
|
let current = codeTable[codeId]
|
||||||
|
colorIndexes.add(current)
|
||||||
|
carryOver = @[current[0]]
|
||||||
|
|
||||||
|
elif codeLast != -1 and codeLast != clearMark and codeLast != endMark:
|
||||||
|
# Its in the current table use it.
|
||||||
|
var previous = codeTable[codeLast]
|
||||||
|
carryOver = @[previous[0]]
|
||||||
|
colorIndexes.add(previous & carryOver)
|
||||||
|
|
||||||
|
if codeTable.len == currentCodeTableMax and bitSize < 12:
|
||||||
|
# We need to expand the codeTable max and the bit size.
|
||||||
|
inc bitSize
|
||||||
|
currentCodeTableMax = (1 shl (bitSize)) - 1
|
||||||
|
|
||||||
|
if codeLast != -1 and codeLast != clearMark and codeLast != endMark:
|
||||||
|
# We had some left over and need to expand table.
|
||||||
|
codeTable.add(codeTable[codeLast] & carryOver)
|
||||||
|
|
||||||
|
codeLast = codeId
|
||||||
|
|
||||||
|
# Convert color indexes into real colors.
|
||||||
|
for j, idx in colorIndexes:
|
||||||
|
result.data[j] = colors[idx]
|
||||||
|
|
||||||
|
of 0x21:
|
||||||
|
# Skip over all extensions (mostly animation information)
|
||||||
|
let extentionType = data.readUint8(i)
|
||||||
|
inc i
|
||||||
|
let byteLen = data.readUint8(i)
|
||||||
|
inc i
|
||||||
|
i += byteLen.int
|
||||||
|
doAssert data.readUint8(i) == 0
|
||||||
|
inc i
|
||||||
|
of 0x3b:
|
||||||
|
# Exit block byte - we are done.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise newException(PixieError, "Invalid GIF block type.")
|
BIN
tests/images/gif/3x5.gif
Normal file
BIN
tests/images/gif/3x5.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 814 B |
BIN
tests/images/gif/3x5.png
Normal file
BIN
tests/images/gif/3x5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 B |
BIN
tests/images/gif/audrey.gif
Normal file
BIN
tests/images/gif/audrey.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
tests/images/gif/audrey.png
Normal file
BIN
tests/images/gif/audrey.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
BIN
tests/images/gif/sunflower.gif
Normal file
BIN
tests/images/gif/sunflower.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
tests/images/gif/sunflower.png
Normal file
BIN
tests/images/gif/sunflower.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
41
tests/test_gif.nim
Normal file
41
tests/test_gif.nim
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import pixie/fileformats/gif, pixie/fileformats/png, print, parseutils, flatty/hexprint, flatty/binlisting
|
||||||
|
|
||||||
|
# let binary = decodeBinListing """
|
||||||
|
# 0: 47 49 46 38 39 61
|
||||||
|
# 6: 03 00
|
||||||
|
# 8: 05 00
|
||||||
|
# A: F7
|
||||||
|
# B: 00
|
||||||
|
# C: 00
|
||||||
|
# D: 00 00 00
|
||||||
|
# 10: 80 00 00
|
||||||
|
# 85: 00 00 00
|
||||||
|
# 30A: FF FF FF
|
||||||
|
# 30D: 21 F9
|
||||||
|
# 30F: 04
|
||||||
|
# 310: 01
|
||||||
|
# 311: 00 00
|
||||||
|
# 313: 10 16
|
||||||
|
# 314: 00
|
||||||
|
# 315: 2C
|
||||||
|
# 316: 00 00 00 00
|
||||||
|
# 31A: 03 00 05 00
|
||||||
|
# 31E: 00
|
||||||
|
# 31F: 08
|
||||||
|
# 320: 0B
|
||||||
|
# 321: 00 51 FC 1B 28 70 A0 C1 83 01 01
|
||||||
|
# 32C: 00
|
||||||
|
# 32D: 3B
|
||||||
|
# """
|
||||||
|
|
||||||
|
# echo hexPrint(binary)
|
||||||
|
# writeFile("tests/images/gif/3x5.gif", binary)
|
||||||
|
|
||||||
|
var img = decodeGIF(readFile("tests/images/gif/3x5.gif"))
|
||||||
|
writeFile("tests/images/gif/3x5.png", img.encodePng())
|
||||||
|
|
||||||
|
var img2 = decodeGIF(readFile("tests/images/gif/audrey.gif"))
|
||||||
|
writeFile("tests/images/gif/audrey.png", img2.encodePng())
|
||||||
|
|
||||||
|
var img3 = decodeGIF(readFile("tests/images/gif/sunflower.gif"))
|
||||||
|
writeFile("tests/images/gif/sunflower.png", img3.encodePng())
|
Loading…
Reference in a new issue