Merge pull request #420 from guzba/master

jpeg
This commit is contained in:
treeform 2022-05-12 22:39:25 -07:00 committed by GitHub
commit a21de1d55a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 93 deletions

View file

@ -64,8 +64,8 @@ type
DecoderState = object DecoderState = object
buffer: string buffer: string
pos, bitCount: int pos, bitsBuffered: int
bits: uint32 bitBuffer: uint32
hitEnd: bool hitEnd: bool
imageHeight, imageWidth: int imageHeight, imageWidth: int
quantizationTables: array[4, array[64, uint8]] quantizationTables: array[4, array[64, uint8]]
@ -89,7 +89,7 @@ template failInvalid(reason = "unable to load") =
## Throw exception with a reason. ## Throw exception with a reason.
raise newException(PixieError, "Invalid JPEG, " & reason) raise newException(PixieError, "Invalid JPEG, " & reason)
template clampByte(x): uint8 = template clampByte(x: int32): uint8 =
## Clamp integer into byte range. ## Clamp integer into byte range.
clamp(x, 0, 0xFF).uint8 clamp(x, 0, 0xFF).uint8
@ -143,6 +143,7 @@ proc decodeDQT(state: var DecoderState) =
proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) = proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) =
## Builds the huffman data structure. ## Builds the huffman data structure.
block: block:
# JPEG spec page 51
var k: int var k: int
for i in 0.uint8 ..< 16: for i in 0.uint8 ..< 16:
for j in 0.uint8 ..< counts[i]: for j in 0.uint8 ..< counts[i]:
@ -152,14 +153,15 @@ proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) =
inc k inc k
huffman.sizes[k] = 0 huffman.sizes[k] = 0
var code, j: int # JPEG spec page 52
var code, k: int
for i in 1.uint8 .. 16: for i in 1.uint8 .. 16:
huffman.deltas[i] = j - code huffman.deltas[i] = k - code
if huffman.sizes[j] == i: if huffman.sizes[k] == i:
while huffman.sizes[j] == i: while huffman.sizes[k] == i:
huffman.codes[j] = code.uint16 huffman.codes[k] = code.uint16
inc code inc code
inc j inc k
if code - 1 >= 1 shl i: if code - 1 >= 1 shl i:
failInvalid() failInvalid()
huffman.maxCodes[i] = code shl (16 - i) huffman.maxCodes[i] = code shl (16 - i)
@ -169,12 +171,12 @@ proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) =
for i in 0 ..< huffman.fast.len: for i in 0 ..< huffman.fast.len:
huffman.fast[i] = 255 huffman.fast[i] = 255
for i in 0 ..< j: for i in 0 ..< k:
let size = huffman.sizes[i] let size = huffman.sizes[i]
if size <= fastBits: if size <= fastBits:
let fast = huffman.codes[i].int shl (fastBits - size) let fast = huffman.codes[i].int shl (fastBits - size)
for k in 0 ..< 1 shl (fastBits - size): for j in 0 ..< 1 shl (fastBits - size):
huffman.fast[fast + k] = i.uint8 huffman.fast[fast + j] = i.uint8
proc decodeDHT(state: var DecoderState) = proc decodeDHT(state: var DecoderState) =
## Decode Define Huffman Table ## Decode Define Huffman Table
@ -281,10 +283,7 @@ proc decodeSOF0(state: var DecoderState) =
component.widthStride = state.numMcuWide * component.yScale * 8 component.widthStride = state.numMcuWide * component.yScale * 8
component.heightStride = state.numMcuHigh * component.xScale * 8 component.heightStride = state.numMcuHigh * component.xScale * 8
component.channel = newMask(component.widthStride, component.heightStride)
component.channel = newMask(
component.widthStride, component.heightStride
)
if state.progressive: if state.progressive:
component.widthCoeff = component.widthStride div 8 component.widthCoeff = component.widthStride div 8
@ -304,8 +303,8 @@ proc decodeSOF2(state: var DecoderState) =
proc reset(state: var DecoderState) = proc reset(state: var DecoderState) =
## Rests the decoder state need for reset markers. ## Rests the decoder state need for reset markers.
state.bits = 0 state.bitBuffer = 0
state.bitCount = 0 state.bitsBuffered = 0
for component in 0 ..< state.components.len: for component in 0 ..< state.components.len:
state.components[component].dcPred = 0 state.components[component].dcPred = 0
state.hitEnd = false state.hitEnd = false
@ -379,60 +378,58 @@ proc decodeSOS(state: var DecoderState) =
if len != 0: if len != 0:
failInvalid() failInvalid()
proc fillBits(state: var DecoderState) = proc fillBitBuffer(state: var DecoderState) =
## When we are low on bits, we need to call this to populate some more. ## When we are low on bits, we need to call this to populate some more.
while true: while state.bitsBuffered < 24:
let b = if state.hitEnd: let b =
if state.hitEnd:
0.uint32 0.uint32
else: else:
state.readUint8().uint32 state.readUint8().uint32
if b == 0xFF: if b == 0xFF:
var c = state.readUint8() var c = state.readUint8()
while c == 0xFF: c = state.readUint8() while c == 0xFF:
c = state.readUint8()
if c != 0: if c != 0:
dec state.pos state.pos -= 2
dec state.pos
state.hitEnd = true state.hitEnd = true
return return
state.bits = state.bits or (b shl (24 - state.bitCount)) state.bitBuffer = state.bitBuffer or (b shl (24 - state.bitsBuffered))
state.bitCount += 8 state.bitsBuffered += 8
if not(state.bitCount <= 24):
break
proc huffmanDecode(state: var DecoderState, tableCurrent, table: int): uint8 = proc huffmanDecode(state: var DecoderState, tableCurrent, table: int): uint8 =
## Decode a uint8 from the huffman table. ## Decode a uint8 from the huffman table.
if state.bitCount < 16: var huffman {.byaddr.} = state.huffmanTables[tableCurrent][table]
state.fillBits()
state.fillBitBuffer()
let let
fastId = (state.bits shr (32 - fastBits)) and ((1 shl fastBits) - 1) fastId = (state.bitBuffer shr (32 - fastBits)) and ((1 shl fastBits) - 1)
fast = state.huffmanTables[tableCurrent][table].fast[fastId] fast = huffman.fast[fastId]
if fast < 255: if fast < 255:
let size = state.huffmanTables[tableCurrent][table].sizes[fast].int let size = huffman.sizes[fast].int
if size > state.bitCount: if size > state.bitsBuffered:
failInvalid() failInvalid()
state.bits = state.bits shl size state.bitBuffer = state.bitBuffer shl size
state.bitCount -= size state.bitsBuffered -= size
return state.huffmanTables[tableCurrent][table].symbols[fast] return huffman.symbols[fast]
var var
tmp = (state.bits shr 16).int tmp = (state.bitBuffer shr 16).int
i = fastBits + 1 i = fastBits + 1
while i < state.huffmanTables[tableCurrent][table].maxCodes.len: while i < huffman.maxCodes.len:
if tmp < state.huffmanTables[tableCurrent][table].maxCodes[i]: if tmp < huffman.maxCodes[i]:
break break
inc i inc i
if i == 17 or i > state.bitCount: if i == 17 or i > state.bitsBuffered:
failInvalid() failInvalid()
let symbolId = (state.bits shr (32 - i)).int + let symbolId = (state.bitBuffer shr (32 - i)).int + huffman.deltas[i]
state.huffmanTables[tableCurrent][table].deltas[i] state.bitBuffer = state.bitBuffer shl i
state.bitsBuffered -= i
state.bits = state.bits shl i return huffman.symbols[symbolId]
state.bitCount -= i
return state.huffmanTables[tableCurrent][table].symbols[symbolId]
template lrot(value: uint32, shift: int): uint32 = template lrot(value: uint32, shift: int): uint32 =
## Left rotate - used for huffman decoding. ## Left rotate - used for huffman decoding.
@ -440,36 +437,36 @@ template lrot(value: uint32, shift: int): uint32 =
proc getBit(state: var DecoderState): int = proc getBit(state: var DecoderState): int =
## Get a single bit. ## Get a single bit.
if state.bitCount < 1: if state.bitsBuffered < 1:
state.fillBits() state.fillBitBuffer()
let k = state.bits let k = state.bitBuffer
state.bits = state.bits shl 1 state.bitBuffer = state.bitBuffer shl 1
dec state.bitCount dec state.bitsBuffered
return (k.int and 0x80000000.int) return (k.int and 0x80000000.int)
proc getBitsAsSignedInt(state: var DecoderState, n: int): int = proc getBitsAsSignedInt(state: var DecoderState, n: int): int =
## Get n number of bits as a signed integer. ## Get n number of bits as a signed integer.
if n notin 0 .. 16: if n notin 0 .. 16:
failInvalid() failInvalid()
if state.bitCount < n: if state.bitsBuffered < n:
state.fillBits() state.fillBitBuffer()
let sign = cast[int32](state.bits) shr 31 let sign = cast[int32](state.bitBuffer) shr 31
var k = lrot(state.bits, n) var k = lrot(state.bitBuffer, n)
state.bits = k and (not bitMasks[n]) state.bitBuffer = k and (not bitMasks[n])
k = k and bitMasks[n] k = k and bitMasks[n]
state.bitCount -= n state.bitsBuffered -= n
result = k.int + (biases[n] and (not sign)) result = k.int + (biases[n] and (not sign))
proc getBitsAsUnsignedInt(state: var DecoderState, n: int): int = proc getBitsAsUnsignedInt(state: var DecoderState, n: int): int =
## Get n number of bits as a unsigned integer. ## Get n number of bits as a unsigned integer.
if n notin 0 .. 16: if n notin 0 .. 16:
failInvalid() failInvalid()
if state.bitCount < n: if state.bitsBuffered < n:
state.fillBits() state.fillBitBuffer()
var k = lrot(state.bits, n) var k = lrot(state.bitBuffer, n)
state.bits = k and (not bitMasks[n]) state.bitBuffer = k and (not bitMasks[n])
k = k and bitMasks[n] k = k and bitMasks[n]
state.bitCount -= n state.bitsBuffered -= n
return k.int return k.int
proc decodeRegularBlock( proc decodeRegularBlock(
@ -481,7 +478,8 @@ proc decodeRegularBlock(
failInvalid() failInvalid()
let let
diff = if t == 0: diff =
if t == 0:
0 0
else: else:
state.getBitsAsSignedInt(t) state.getBitsAsSignedInt(t)
@ -768,12 +766,12 @@ proc decodeBlock(state: var DecoderState, comp, row, column: int) =
else: else:
state.decodeRegularBlock(comp, data) state.decodeRegularBlock(comp, data)
template checkReset(state: var DecoderState) = proc checkReset(state: var DecoderState) =
## Check if we might have run into a reset marker, then deal with it. ## Check if we might have run into a reset marker, then deal with it.
dec state.todo dec state.todo
if state.todo <= 0: if state.todo <= 0:
if state.bitCount < 24: if state.bitsBuffered < 24:
state.fillBits() state.fillBitBuffer()
if state.buffer[state.pos] == 0xFF.char: if state.buffer[state.pos] == 0xFF.char:
if state.buffer[state.pos+1] in {0xD0.char .. 0xD7.char}: if state.buffer[state.pos+1] in {0xD0.char .. 0xD7.char}:
@ -786,7 +784,6 @@ proc decodeBlocks(state: var DecoderState) =
## Decodes scan data blocks that follow a SOS block. ## Decodes scan data blocks that follow a SOS block.
if state.scanComponents == 1: if state.scanComponents == 1:
# Single component pass. # Single component pass.
let let
comp = state.componentOrder[0] comp = state.componentOrder[0]
w = (state.components[comp].width + 7) shr 3 w = (state.components[comp].width + 7) shr 3
@ -814,7 +811,6 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
let let
w = (state.components[comp].width + 7) shr 3 w = (state.components[comp].width + 7) shr 3
h = (state.components[comp].height + 7) shr 3 h = (state.components[comp].height + 7) shr 3
for column in 0 ..< h: for column in 0 ..< h:
for row in 0 ..< w: for row in 0 ..< w:
var data = state.components[comp].blocks[row][column] var data = state.components[comp].blocks[row][column]
@ -838,13 +834,13 @@ proc magnifyXBy2(mask: Mask): Mask =
let n = 3 * mask.unsafe[x, y].uint16 let n = 3 * mask.unsafe[x, y].uint16
if x == 0: if x == 0:
result.unsafe[x * 2 + 0, y] = mask.unsafe[x, y] result.unsafe[x * 2 + 0, y] = mask.unsafe[x, y]
result.unsafe[x * 2 + 1, y] = ((n + mask.unsafe[x+1, y].uint16 + 2) div 4).uint8 result.unsafe[x * 2 + 1, y] = ((n + mask.unsafe[x + 1, y].uint16 + 2) div 4).uint8
elif x == mask.width - 1: elif x == mask.width - 1:
result.unsafe[x * 2 + 0, y] = ((n + mask.unsafe[x-1, y].uint16 + 2) div 4).uint8 result.unsafe[x * 2 + 0, y] = ((n + mask.unsafe[x - 1, y].uint16 + 2) div 4).uint8
result.unsafe[x * 2 + 1, y] = mask.unsafe[x, y] result.unsafe[x * 2 + 1, y] = mask.unsafe[x, y]
else: else:
result.unsafe[x * 2 + 0, y] = ((n + mask.unsafe[x-1, y].uint16) div 4).uint8 result.unsafe[x * 2 + 0, y] = ((n + mask.unsafe[x - 1, y].uint16) div 4).uint8
result.unsafe[x * 2 + 1, y] = ((n + mask.unsafe[x+1, y].uint16) div 4).uint8 result.unsafe[x * 2 + 1, y] = ((n + mask.unsafe[x + 1, y].uint16) div 4).uint8
proc magnifyYBy2(mask: Mask): Mask = proc magnifyYBy2(mask: Mask): Mask =
## Smooth magnify by power of 2 only in the Y direction. ## Smooth magnify by power of 2 only in the Y direction.
@ -854,23 +850,23 @@ proc magnifyYBy2(mask: Mask): Mask =
let n = 3 * mask.unsafe[x, y].uint16 let n = 3 * mask.unsafe[x, y].uint16
if y == 0: if y == 0:
result.unsafe[x, y * 2 + 0] = mask.unsafe[x, y] result.unsafe[x, y * 2 + 0] = mask.unsafe[x, y]
result.unsafe[x, y * 2 + 1] = ((n + mask.unsafe[x, y+1].uint16 + 2) div 4).uint8 result.unsafe[x, y * 2 + 1] = ((n + mask.unsafe[x, y + 1].uint16 + 2) div 4).uint8
elif y == mask.height - 1: elif y == mask.height - 1:
result.unsafe[x, y * 2 + 0] = ((n + mask.unsafe[x, y-1].uint16 + 2) div 4).uint8 result.unsafe[x, y * 2 + 0] = ((n + mask.unsafe[x, y - 1].uint16 + 2) div 4).uint8
result.unsafe[x, y * 2 + 1] = mask.unsafe[x, y] result.unsafe[x, y * 2 + 1] = mask.unsafe[x, y]
else: else:
result.unsafe[x, y * 2 + 0] = ((n + mask.unsafe[x, y-1].uint16) div 4).uint8 result.unsafe[x, y * 2 + 0] = ((n + mask.unsafe[x, y - 1].uint16) div 4).uint8
result.unsafe[x, y * 2 + 1] = ((n + mask.unsafe[x, y+1].uint16) div 4).uint8 result.unsafe[x, y * 2 + 1] = ((n + mask.unsafe[x, y + 1].uint16) div 4).uint8
proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX = proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX =
## Takes a 3 component yCbCr outputs and populates image. ## Takes a 3 component yCbCr outputs and populates image.
template float2Fixed(x: float32): int = template float2Fixed(x: float32): int32 =
(x * 4096 + 0.5).int shl 8 (x * 4096 + 0.5).int32 shl 8
let let
yFixed = (py.int shl 20) + (1 shl 19) yFixed = (py.int32 shl 20) + (1 shl 19)
cb = pcb.int - 128 cb = pcb.int32 - 128
cr = pcr.int - 128 cr = pcr.int32 - 128
var var
r = yFixed + cr * float2Fixed(1.40200) r = yFixed + cr * float2Fixed(1.40200)
g = yFixed + g = yFixed +
@ -882,13 +878,9 @@ proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX =
result.b = clampByte(b shr 20) result.b = clampByte(b shr 20)
result.a = 255 result.a = 255
proc grayScaleToRgbx(gray: uint8): ColorRGBX = proc grayScaleToRgbx(gray: uint8): ColorRGBX {.inline.} =
## Takes a single gray scale component output and populates image. ## Takes a single gray scale component output and populates image.
let g = gray rgbx(gray, gray, gray, 255)
result.r = g
result.g = g
result.b = g
result.a = 255
proc buildImage(state: var DecoderState): Image = proc buildImage(state: var DecoderState): Image =
## Takes a jpeg image object and builds a pixie Image from it. ## Takes a jpeg image object and builds a pixie Image from it.

View file

@ -2,5 +2,5 @@ import benchy, jpegsuite, pixie/fileformats/jpg, strformat
for file in jpegSuiteFiles: for file in jpegSuiteFiles:
let data = readFile(file) let data = readFile(file)
timeIt &"jpeg {(data.len div 1024)}k decode", 10000: timeIt &"jpeg {(data.len div 1024)}k decode":
discard decodeJpg(data) discard decodeJpg(data)