Merge pull request #148 from guzba/master

use zippy bitstream in gif.nim
This commit is contained in:
treeform 2021-03-01 10:14:43 -08:00 committed by GitHub
commit 5c8204b1cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 47 deletions

View file

@ -1,43 +1,22 @@
import chroma, flatty/binny, pixie/common, pixie/images, math
import chroma, flatty/binny, pixie/common, pixie/images, math, zippy/bitstreams
const gifSignatures* = @["GIF87a", "GIF89a"]
# See: https://en.wikipedia.org/wiki/GIF
type
BitStream* = ref object
data: seq[uint8] # data
pos: int # position in bits
len: int # len in bits
proc newBitStream*(data: string): BitStream =
result = BitStream()
result.data = cast[seq[uint8]](data)
result.len = result.data.len * 8
proc readBit(bs: BitStream, pos: int): int =
let byteIndex = pos div 8
let bitIndex = pos mod 8
result = bs.data[byteIndex].int shr (bitIndex) and 1
proc read*(bs: BitStream, bits: int): int =
## Reads number of bits
# TODO: This can be faster.
for i in 0 ..< bits:
result = result shl 1
result += bs.readBit(bs.pos + bits - i - 1)
bs.pos += bits
template failInvalid() =
raise newException(PixieError, "Invalid GIF buffer, unable to load.")
raise newException(PixieError, "Invalid GIF buffer, unable to load")
proc decodeGIF*(data: string): Image =
when defined(release):
{.push checks: off.}
proc decodeGif*(data: string): Image =
## Decodes GIF data into an Image.
if data.len <= 0xD: failInvalid()
if data.len <= 13: failInvalid()
let version = data[0 .. 5]
if version notin gifSignatures:
raise newException(PixieError, "Invalid GIF file signature.")
raise newException(PixieError, "Invalid GIF file signature")
let
# Read information about the image.
@ -54,8 +33,9 @@ proc decodeGIF*(data: string): Image =
result = newImage(width, height)
# Read the main color table.
var colors: seq[ColorRGBA]
var i = 0xD
var
colors: seq[ColorRGBA]
i = 13
if hasColorTable:
if i + colorTableSize * 3 >= data.len: failInvalid()
for c in 0 ..< colorTableSize:
@ -76,8 +56,8 @@ proc decodeGIF*(data: string): Image =
let
left = data.readUint16(i + 0)
top = data.readUint16(i + 2)
width = data.readUint16(i + 4)
height = data.readUint16(i + 6)
w = data.readUint16(i + 4).int
h = data.readUint16(i + 6).int
flags = data.readUint8(i + 8)
hasColorTable = (flags and 0x80) != 0
@ -88,15 +68,16 @@ proc decodeGIF*(data: string): Image =
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 left != 0 or top != 0 or w != result.width or h != result.height:
raise newException(PixieError, "GIF block offsets not supported")
if hasColorTable:
raise newException(PixieError, "Color table per block not supported.")
raise newException(
PixieError, "GIF color table per block not yet supported"
)
if interlace:
raise newException(PixieError, "Interlacing not supported.")
raise newException(PixieError, "Interlaced GIF not yet supported")
# Read the lzw data chunks.
if i >= data.len: failInvalid()
@ -120,7 +101,7 @@ proc decodeGIF*(data: string): Image =
# Turn full lzw data into bit stream.
var
bs = newBitStream(lzwData)
bs = initBitStream(cast[seq[uint8]](lzwData))
bitSize = lzwMinBitSize + 1
currentCodeTableMax = (1 shl (bitSize)) - 1
codeLast: int = -1
@ -129,11 +110,10 @@ proc decodeGIF*(data: string): Image =
# Main decode loop.
while codeLast != endCode:
if bs.pos + bitSize.int > bs.len: failInvalid()
if bs.pos + bitSize.int > bs.data.len * 8: failInvalid()
var
# Read variable bits out of the table.
codeId = bs.read(bitSize.int)
codeId = bs.readBits(bitSize.int).int
# Some time we need to carry over table information.
carryOver: seq[int]
@ -193,4 +173,7 @@ proc decodeGIF*(data: string): Image =
# Exit block byte - we are done.
return
else:
raise newException(PixieError, "Invalid GIF block type.")
raise newException(PixieError, "Invalid GIF block type")
when defined(release):
{.pop.}

6
tests/benchmark_gif.nim Normal file
View file

@ -0,0 +1,6 @@
import benchy, pixie/fileformats/gif
let data = readFile("tests/images/gif/audrey.gif")
timeIt "pixie decode":
keep decodeGif(data)

View file

@ -1,13 +1,13 @@
import pixie/fileformats/gif, pixie/fileformats/png, pixie
import pixie/fileformats/gif, pixie
var img = decodeGIF(readFile("tests/images/gif/3x5.gif"))
writeFile("tests/images/gif/3x5.png", img.encodePng())
img.writeFile("tests/images/gif/3x5.png")
var img2 = decodeGIF(readFile("tests/images/gif/audrey.gif"))
writeFile("tests/images/gif/audrey.png", img2.encodePng())
img2.writeFile("tests/images/gif/audrey.png")
var img3 = decodeGIF(readFile("tests/images/gif/sunflower.gif"))
writeFile("tests/images/gif/sunflower.png", img3.encodePng())
img3.writeFile("tests/images/gif/sunflower.png")
var img4 = readImage("tests/images/gif/sunflower.gif")
doAssert img3.data == img4.data