Merge pull request #416 from treeform/dev

More JPEG parser cleanup.
This commit is contained in:
treeform 2022-05-10 20:43:38 -07:00 committed by GitHub
commit 6fa7c7abd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 223 additions and 281 deletions

1
.gitignore vendored
View file

@ -15,3 +15,4 @@ bindings/generated
*.dSYM *.dSYM
dump.txt dump.txt
tests/fileformats/jpeg/generated tests/fileformats/jpeg/generated
tests/fileformats/jpeg/diff

View file

@ -1,4 +1,4 @@
import pixie/common, pixie/images, sequtils, strutils import pixie/common, pixie/images, pixie/masks, sequtils, strutils, chroma, std/decls
# This JPEG decoder is loosely based on stb_image which is public domain. # This JPEG decoder is loosely based on stb_image which is public domain.
@ -50,16 +50,6 @@ type
maxCodes: array[18, int] maxCodes: array[18, int]
fast: array[1 shl fastBits, uint8] fast: array[1 shl fastBits, uint8]
ResampleProc = proc(dst, line0, line1: ptr UncheckedArray[uint8],
widthPreExpansion, horizontalExpansionFactor: int
): ptr UncheckedArray[uint8] {.raises: [].}
Resample = object
horizontalExpansionFactor, verticalExpansionFactor: int
yStep, yPos, widthPreExpansion: int
line0, line1: ptr UncheckedArray[uint8]
resample: ResampleProc
Component = object Component = object
id, quantizationTableId: uint8 id, quantizationTableId: uint8
yScale, xScale: int yScale, xScale: int
@ -68,8 +58,9 @@ type
huffmanDC, huffmanAC: int huffmanDC, huffmanAC: int
dcPred: int dcPred: int
widthCoeff, heightCoeff: int widthCoeff, heightCoeff: int
data, coeff, lineBuf: seq[uint8] coeff, lineBuf: seq[uint8]
blocks: seq[seq[array[64, int16]]] blocks: seq[seq[array[64, int16]]]
channel: Mask
DecoderState = object DecoderState = object
buffer: seq[uint8] buffer: seq[uint8]
@ -91,6 +82,9 @@ type
todo: int todo: int
eobRun: int eobRun: int
when defined(release):
{.push checks: off.}
template failInvalid(reason = "unable to load") = 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)
@ -99,10 +93,6 @@ template clampByte(x): uint8 =
## Clamp integer into byte range. ## Clamp integer into byte range.
clamp(x, 0, 0xFF).uint8 clamp(x, 0, 0xFF).uint8
template clampInt16(x): int16 =
## Clamp integer into byte range.
clamp(x, -32768, 32767).int16
proc readUint8(state: var DecoderState): uint8 = proc readUint8(state: var DecoderState): uint8 =
## Reads a byte from the input stream. ## Reads a byte from the input stream.
if state.pos >= state.buffer.len: if state.pos >= state.buffer.len:
@ -151,6 +141,7 @@ proc decodeDQT(state: var DecoderState) =
failInvalid("DQT table length did not match") failInvalid("DQT table length did not match")
proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) = proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) =
## Builds the huffman data structure.
block: block:
var k: int var k: int
for i in 0.uint8 ..< 16: for i in 0.uint8 ..< 16:
@ -187,7 +178,6 @@ proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) =
proc decodeDHT(state: var DecoderState) = proc decodeDHT(state: var DecoderState) =
## Decode Define Huffman Table ## Decode Define Huffman Table
var len = state.readUint16be() - 2 var len = state.readUint16be() - 2
while len > 0: while len > 0:
let let
@ -239,8 +229,8 @@ proc decodeSOF0(state: var DecoderState) =
failInvalid("unsupported component count, must be 1 or 3") failInvalid("unsupported component count, must be 1 or 3")
for i in 0 ..< numComponents: for i in 0 ..< numComponents:
state.components.add(Component()) var component = Component()
state.components[i].id = state.readUint8() component.id = state.readUint8()
let let
info = state.readUint8() info = state.readUint8()
vertical = info and 15 vertical = info and 15
@ -253,19 +243,14 @@ proc decodeSOF0(state: var DecoderState) =
if vertical == 0 or vertical > 4 or horizontal == 0 or horizontal > 4: if vertical == 0 or vertical > 4 or horizontal == 0 or horizontal > 4:
failInvalid("invalid component scaling factor") failInvalid("invalid component scaling factor")
state.components[i].xScale = vertical.int component.xScale = vertical.int
state.components[i].yScale = horizontal.int component.yScale = horizontal.int
state.components[i].quantizationTableId = quantizationTableId component.quantizationTableId = quantizationTableId
state.components.add(component)
for i in 0 ..< state.components.len: for component in state.components.mitems:
state.maxXScale = max( state.maxXScale = max(state.maxXScale, component.xScale)
state.maxXScale, state.maxYScale = max(state.maxYScale, component.yScale)
state.components[i].xScale
)
state.maxYScale = max(
state.maxYScale,
state.components[i].yScale
)
state.mcuWidth = state.maxYScale * 8 state.mcuWidth = state.maxYScale * 8
state.mcuHeight = state.maxXScale * 8 state.mcuHeight = state.maxXScale * 8
@ -274,40 +259,38 @@ proc decodeSOF0(state: var DecoderState) =
state.numMcuHigh = state.numMcuHigh =
(state.imageHeight + state.mcuHeight - 1) div state.mcuHeight (state.imageHeight + state.mcuHeight - 1) div state.mcuHeight
for i in 0 ..< state.components.len: for component in state.components.mitems:
state.components[i].width = ( component.width = (
state.imageWidth * state.imageWidth *
state.components[i].yScale + component.yScale +
state.maxYScale - 1 state.maxYScale - 1
) div state.maxYScale ) div state.maxYScale
state.components[i].height = ( component.height = (
state.imageHeight * state.imageHeight *
state.components[i].xScale + component.xScale +
state.maxXScale - 1 state.maxXScale - 1
) div state.maxXScale ) div state.maxXScale
# Allocate block data structures. # Allocate block data structures.
state.components[i].blocks = newSeqWith( component.blocks = newSeqWith(
state.components[i].width, component.width,
newSeq[array[64, int16]]( newSeq[array[64, int16]](
state.components[i].height component.height
) )
) )
state.components[i].widthStride = component.widthStride = state.numMcuWide * component.yScale * 8
state.numMcuWide * state.components[i].yScale * 8 component.heightStride = state.numMcuHigh * component.xScale * 8
state.components[i].heightStride =
state.numMcuHigh * state.components[i].xScale * 8
state.components[i].data.setLen( component.channel = newMask(
state.components[i].widthStride * state.components[i].heightStride component.widthStride, component.heightStride
) )
if state.progressive: if state.progressive:
state.components[i].widthCoeff = state.components[i].widthStride div 8 component.widthCoeff = component.widthStride div 8
state.components[i].heightCoeff = state.components[i].heightStride div 8 component.heightCoeff = component.heightStride div 8
state.components[i].coeff.setLen( component.coeff.setLen(
state.components[i].widthStride * state.components[i].heightStride component.widthStride * component.heightStride
) )
proc decodeSOF1(state: var DecoderState) = proc decodeSOF1(state: var DecoderState) =
@ -320,6 +303,7 @@ proc decodeSOF2(state: var DecoderState) =
state.progressive = true state.progressive = true
proc reset(state: var DecoderState) = proc reset(state: var DecoderState) =
## Rests the decoder state need for reset markers.
state.bits = 0 state.bits = 0
state.bitCount = 0 state.bitCount = 0
for component in 0 ..< state.components.len: for component in 0 ..< state.components.len:
@ -343,7 +327,7 @@ proc decodeSOS(state: var DecoderState) =
if state.scanComponents notin [1, 3]: if state.scanComponents notin [1, 3]:
failInvalid("unsupported scan component count") failInvalid("unsupported scan component count")
state.componentOrder = @[] state.componentOrder.setLen(0)
for i in 0 ..< state.scanComponents: for i in 0 ..< state.scanComponents:
let let
@ -488,7 +472,7 @@ proc getBitsAsUnsignedInt(state: var DecoderState, n: int): int =
state.bitCount -= n state.bitCount -= n
return k.int return k.int
{.push overflowChecks: off.} {.push overflowChecks: off, rangeChecks: off.}
proc decodeRegularBlock( proc decodeRegularBlock(
state: var DecoderState, component: int, data: var array[64, int16] state: var DecoderState, component: int, data: var array[64, int16]
@ -505,7 +489,7 @@ proc decodeRegularBlock(
state.getBitsAsSignedInt(t) state.getBitsAsSignedInt(t)
dc = state.components[component].dcPred + diff dc = state.components[component].dcPred + diff
state.components[component].dcPred = dc state.components[component].dcPred = dc
data[0] = clampInt16(dc) data[0] = dc.int16
var i = 1 var i = 1
while true: while true:
@ -547,7 +531,7 @@ proc decodeProgressiveBlock(
let let
dc = state.components[component].dcPred + diff dc = state.components[component].dcPred + diff
state.components[component].dcPred = dc state.components[component].dcPred = dc
data[0] = clampInt16(dc * (1 shl state.successiveApproxLow)) data[0] = (dc * (1 shl state.successiveApproxLow)).int16
else: else:
if getBit(state) != 0: if getBit(state) != 0:
@ -592,7 +576,7 @@ proc decodeProgressiveContinuationBlock(
inc k inc k
if s >= 15: if s >= 15:
failInvalid() failInvalid()
data[zig] = clampInt16(state.getBitsAsSignedInt(s.int) * (1 shl shift)) data[zig] = (state.getBitsAsSignedInt(s.int) * (1 shl shift)).int16
if not(k <= state.spectralEnd): if not(k <= state.spectralEnd):
break break
@ -657,6 +641,7 @@ proc decodeProgressiveContinuationBlock(
break break
template idct1D(s0, s1, s2, s3, s4, s5, s6, s7: int32) = template idct1D(s0, s1, s2, s3, s4, s5, s6, s7: int32) =
## Inverse discrete cosine transform 1D
template f2f(x: float32): int32 = (x * 4096 + 0.5).int32 template f2f(x: float32): int32 = (x * 4096 + 0.5).int32
template fsh(x: int32): int32 = x * 4096 template fsh(x: int32): int32 = x * 4096
p2 = s2 p2 = s2
@ -695,6 +680,7 @@ template idct1D(s0, s1, s2, s3, s4, s5, s6, s7: int32) =
t0 += p1 + p3 t0 += p1 + p3
proc idctBlock(component: var Component, offset: int, data: array[64, int16]) = proc idctBlock(component: var Component, offset: int, data: array[64, int16]) =
## Inverse discrete cosine transform whole block.
var values: array[64, int32] var values: array[64, int32]
for i in 0 ..< 8: for i in 0 ..< 8:
if data[i + 8] == 0 and if data[i + 8] == 0 and
@ -704,7 +690,7 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) =
data[i + 40] == 0 and data[i + 40] == 0 and
data[i + 48] == 0 and data[i + 48] == 0 and
data[i + 56] == 0: data[i + 56] == 0:
let dcterm = clampInt16(data[i].int * 4.int) let dcterm = data[i] * 4
values[i + 0] = dcterm values[i + 0] = dcterm
values[i + 8] = dcterm values[i + 8] = dcterm
values[i + 16] = dcterm values[i + 16] = dcterm
@ -760,20 +746,18 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) =
x2 += 65536 + (128 shl 17) x2 += 65536 + (128 shl 17)
x3 += 65536 + (128 shl 17) x3 += 65536 + (128 shl 17)
component.data[outPos + 0] = clampByte((x0 + t3) shr 17) component.channel.data[outPos + 0] = clampByte((x0 + t3) shr 17)
component.data[outPos + 7] = clampByte((x0 - t3) shr 17) component.channel.data[outPos + 7] = clampByte((x0 - t3) shr 17)
component.data[outPos + 1] = clampByte((x1 + t2) shr 17) component.channel.data[outPos + 1] = clampByte((x1 + t2) shr 17)
component.data[outPos + 6] = clampByte((x1 - t2) shr 17) component.channel.data[outPos + 6] = clampByte((x1 - t2) shr 17)
component.data[outPos + 2] = clampByte((x2 + t1) shr 17) component.channel.data[outPos + 2] = clampByte((x2 + t1) shr 17)
component.data[outPos + 5] = clampByte((x2 - t1) shr 17) component.channel.data[outPos + 5] = clampByte((x2 - t1) shr 17)
component.data[outPos + 3] = clampByte((x3 + t0) shr 17) component.channel.data[outPos + 3] = clampByte((x3 + t0) shr 17)
component.data[outPos + 4] = clampByte((x3 - t0) shr 17) component.channel.data[outPos + 4] = clampByte((x3 - t0) shr 17)
{.pop.}
proc decodeBlock(state: var DecoderState, comp, row, column: int) = proc decodeBlock(state: var DecoderState, comp, row, column: int) =
## Decodes a block. ## Decodes a block.
var data = state.components[comp].blocks[row][column] var data {.byaddr.} = state.components[comp].blocks[row][column]
if state.progressive: if state.progressive:
if state.spectralStart == 0: if state.spectralStart == 0:
state.decodeProgressiveBlock(comp, data) state.decodeProgressiveBlock(comp, data)
@ -781,9 +765,9 @@ proc decodeBlock(state: var DecoderState, comp, row, column: int) =
state.decodeProgressiveContinuationBlock(comp, data) state.decodeProgressiveContinuationBlock(comp, data)
else: else:
state.decodeRegularBlock(comp, data) state.decodeRegularBlock(comp, data)
state.components[comp].blocks[row][column] = data
template checkReset(state: var DecoderState) = template checkReset(state: var DecoderState) =
## 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.bitCount < 24:
@ -794,45 +778,32 @@ template checkReset(state: var DecoderState) =
state.pos += 2 state.pos += 2
else: else:
failInvalid("did not get expected reset marker") failInvalid("did not get expected reset marker")
state.reset() state.reset()
proc decodeBlocks(state: var DecoderState) = 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.progressive: 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
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:
state.decodeBlock(comp, row, column) state.decodeBlock(comp, row, column)
state.checkReset() state.checkReset()
else:
# Interleaved component pass.
for y in 0 ..< state.numMcuHigh:
for x in 0 ..< state.numMcuWide:
for comp in state.componentOrder:
for j in 0 ..< state.components[comp].yScale:
for i in 0 ..< state.components[comp].xScale:
let
row = (x * state.components[comp].xScale + i)
column = (y * state.components[comp].yScale + j)
state.decodeBlock(comp, row, column)
state.checkReset()
else: else:
# Interleaved regular component pass. # Interleaved regular component pass.
for y in 0 ..< state.numMcuHigh: for mcuY in 0 ..< state.numMcuHigh:
for x in 0 ..< state.numMcuWide: for mcuX in 0 ..< state.numMcuWide:
for comp in state.componentOrder: for comp in state.componentOrder:
for j in 0 ..< state.components[comp].xScale: for compY in 0 ..< state.components[comp].xScale:
for i in 0 ..< state.components[comp].yScale: for compX in 0 ..< state.components[comp].yScale:
let let
row = (x * state.components[comp].yScale + i) row = (mcuX * state.components[comp].yScale + compX)
column = (y * state.components[comp].xScale + j) col = (mcuY * state.components[comp].xScale + compY)
state.decodeBlock(comp, row, column) state.decodeBlock(comp, row, col)
state.checkReset() state.checkReset()
proc quantizationAndIDCTPass(state: var DecoderState) = proc quantizationAndIDCTPass(state: var DecoderState) =
@ -850,206 +821,113 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
let qTableId = state.components[comp].quantizationTableId let qTableId = state.components[comp].quantizationTableId
if qTableId.int notin 0 ..< state.quantizationTables.len: if qTableId.int notin 0 ..< state.quantizationTables.len:
failInvalid() failInvalid()
data[i] = clampInt16(data[i] * state.quantizationTables[qTableId][i].int) data[i] = data[i] * state.quantizationTables[qTableId][i].int16
state.components[comp].idctBlock( state.components[comp].idctBlock(
state.components[comp].widthStride * column * 8 + row * 8, state.components[comp].widthStride * column * 8 + row * 8,
data data
) )
proc resampleRowH1V1( proc magnifyXBy2(mask: Mask): Mask =
dst, a, b: ptr UncheckedArray[uint8], ## Smooth magnify by power of 2 only in the X direction.
widthPreExpansion, horizontalExpansionFactor: int result = newMask(mask.width * 2, mask.height)
): ptr UncheckedArray[uint8] = for y in 0 ..< mask.height:
a for x in 0 ..< mask.width:
let n = 3 * mask.unsafe[x, y].uint16
if x == 0:
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
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 + 1, y] = mask.unsafe[x, y]
else:
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
proc resampleRowH1V2( proc magnifyYBy2(mask: Mask): Mask =
dst, a, b: ptr UncheckedArray[uint8], ## Smooth magnify by power of 2 only in the Y direction.
widthPreExpansion, horizontalExpansionFactor: int result = newMask(mask.width, mask.height * 2)
): ptr UncheckedArray[uint8] = for y in 0 ..< mask.height:
for i in 0 ..< widthPreExpansion: for x in 0 ..< mask.width:
dst[i] = ((3 * a[i].int + b[i].int + 2) shr 2).uint8 let n = 3 * mask.unsafe[x, y].uint16
dst if y == 0:
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
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 + 1] = mask.unsafe[x, y]
else:
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
proc resampleRowH2V1( proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX =
dst, a, b: ptr UncheckedArray[uint8],
widthPreExpansion, horizontalExpansionFactor: int
): ptr UncheckedArray[uint8] =
if widthPreExpansion == 1:
dst[0] = a[0]
dst[1] = dst[0]
else:
dst[0] = a[0]
dst[1] = ((a[0].int * 3 + a[1].int + 2) shr 2).uint8
for i in 1 ..< widthPreExpansion - 1:
let n = 3 * a[i].int + 2
dst[i * 2 + 0] = ((n + a[i - 1].int) shr 2).uint8
dst[i * 2 + 1] = ((n + a[i + 1].int) shr 2).uint8
dst[widthPreExpansion * 2 + 0] = ((
a[widthPreExpansion - 2].int * 3 + a[widthPreExpansion - 1].int + 2
) shr 2).uint8
dst[widthPreExpansion * 2 + 1] = (a[widthPreExpansion - 1]) shr 2
dst
proc resampleRowH4V1(
dst, a, b: ptr UncheckedArray[uint8],
widthPreExpansion, horizontalExpansionFactor: int
): ptr UncheckedArray[uint8] =
for i in 0 ..< widthPreExpansion * 4:
dst[i] = a[i div 4]
dst
proc resampleRowH2V2(
dst, a, b: ptr UncheckedArray[uint8],
widthPreExpansion, horizontalExpansionFactor: int
): ptr UncheckedArray[uint8] =
if widthPreExpansion == 1:
dst[0] = ((3 * a[0].int + b[0].int + 2) shr 2).uint8
dst[1] = dst[0]
else:
var
t0: int
t1 = 3 * a[0].int + b[0].int
dst[0] = ((t1 + 2) shr 2).uint8
for i in 1 ..< widthPreExpansion:
t0 = t1
t1 = 3 * a[i].int + b[i].int
dst[i * 2 - 1] = ((3 * t0 + t1 + 8) shr 4).uint8
dst[i * 2 + 0] = ((3 * t1 + t0 + 8) shr 4).uint8
dst[widthPreExpansion * 2 - 1] = ((t1 + 2) shr 2).uint8
dst
proc yCbCrToRgbx(dst, py, pcb, pcr: ptr UncheckedArray[uint8], width: int) =
## 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): int =
(x * 4096 + 0.5).int shl 8 (x * 4096 + 0.5).int shl 8
var pos: int let
for i in 0 ..< width: yFixed = (py.int shl 20) + (1 shl 19)
let cb = pcb.int - 128
yFixed = (py[][i].int shl 20) + (1 shl 19) cr = pcr.int - 128
cb = pcb[][i].int - 128 var
cr = pcr[][i].int - 128 r = yFixed + cr * float2Fixed(1.40200)
var g = yFixed +
r = yFixed + cr * float2Fixed(1.40200) (cr * -float2Fixed(0.71414)) +
g = yFixed + ((cb * -float2Fixed(0.34414)) and -65536)
(cr * -float2Fixed(0.71414)) + b = yFixed + cb * float2Fixed(1.77200)
((cb * -float2Fixed(0.34414)) and -65536) result.r = clampByte(r shr 20)
b = yFixed + cb * float2Fixed(1.77200) result.g = clampByte(g shr 20)
dst[pos + 0] = clampByte(r shr 20) result.b = clampByte(b shr 20)
dst[pos + 1] = clampByte(g shr 20) result.a = 255
dst[pos + 2] = clampByte(b shr 20)
dst[pos + 3] = 255
pos += 4
proc grayScaleToRgbx(dst, gray: ptr UncheckedArray[uint8], width: int) = proc grayScaleToRgbx(gray: uint8): ColorRGBX =
## Takes a single gray scale component output and populates image. ## Takes a single gray scale component output and populates image.
var pos: int let g = gray
for i in 0 ..< width: result.r = g
let g = gray[i] result.g = g
dst[pos + 0] = g result.b = g
dst[pos + 1] = g result.a = 255
dst[pos + 2] = g
dst[pos + 3] = 255 {.pop.}
pos += 4
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.
if state.components.len == 3:
for component in state.components.mitems:
while component.yScale < state.maxYScale:
component.channel = component.channel.magnifyXBy2()
component.yScale *= 2
while component.xScale < state.maxXScale:
component.channel = component.channel.magnifyYBy2()
component.xScale *= 2
result = newImage(state.imageWidth, state.imageHeight) result = newImage(state.imageWidth, state.imageHeight)
var resamples: array[3, Resample] if state.components.len == 3:
for i in 0 ..< state.components.len: let
resamples[i].horizontalExpansionFactor = cy = state.components[0].channel
state.maxYScale div cb = state.components[1].channel
state.components[i].yScale cr = state.components[2].channel
resamples[i].verticalExpansionFactor = for y in 0 ..< state.imageHeight:
state.maxXScale div for x in 0 ..< state.imageWidth:
state.components[i].xScale result.unsafe[x, y] = yCbCrToRgbx(
resamples[i].yStep = resamples[i].verticalExpansionFactor shr 1 cy.unsafe[x, y],
resamples[i].widthPreExpansion = ( cb.unsafe[x, y],
state.imageWidth + resamples[i].horizontalExpansionFactor - 1 cr.unsafe[x, y],
) div resamples[i].horizontalExpansionFactor )
resamples[i].line0 = cast[ptr UncheckedArray[uint8]]( elif state.components.len == 1:
state.components[i].data[0].addr let
) cy = state.components[0].channel
resamples[i].line1 = cast[ptr UncheckedArray[uint8]]( for y in 0 ..< state.imageHeight:
state.components[i].data[0].addr for x in 0 ..< state.imageWidth:
) result.unsafe[x, y] = grayScaleToRgbx(
state.components[i].lineBuf.setLen(state.imageWidth + 3) cy.unsafe[x, y],
)
if resamples[i].horizontalExpansionFactor == 1 and else:
resamples[i].verticalExpansionFactor == 1: failInvalid()
resamples[i].resample = resampleRowH1V1
elif resamples[i].horizontalExpansionFactor == 1 and
resamples[i].verticalExpansionFactor == 2:
resamples[i].resample = resampleRowH1V2
elif resamples[i].horizontalExpansionFactor == 2 and
resamples[i].verticalExpansionFactor == 1:
resamples[i].resample = resampleRowH2V1
elif resamples[i].horizontalExpansionFactor == 2 and
resamples[i].verticalExpansionFactor == 2:
resamples[i].resample = resampleRowH2V2
elif resamples[i].horizontalExpansionFactor == 4 and
resamples[i].verticalExpansionFactor == 1:
resamples[i].resample = resampleRowH4V1
else:
failInvalid()
for y in 0 ..< state.imageHeight:
var componentOutputs: seq[ptr UncheckedArray[uint8]]
for i in 0 ..< state.components.len:
let yBottom =
resamples[i].yStep >= (resamples[i].verticalExpansionFactor shr 1)
# TODO
# for x in sample ^ 2
# resample x dir
# for y in sample ^ 2
# resample y dir
componentOutputs.add resamples[i].resample(
cast[ptr UncheckedArray[uint8]](state.components[i].lineBuf[0].addr),
if yBottom: resamples[i].line1 else: resamples[i].line0,
if yBottom: resamples[i].line0 else: resamples[i].line1,
resamples[i].widthPreExpansion,
resamples[i].horizontalExpansionFactor
)
inc resamples[i].yStep
if resamples[i].yStep >= resamples[i].verticalExpansionFactor:
resamples[i].yStep = 0
resamples[i].line0 = resamples[i].line1
inc resamples[i].yPos
if resamples[i].yPos < state.components[i].height:
resamples[i].line1 = cast[ptr UncheckedArray[uint8]](
state.components[i].data[
resamples[i].yPos * state.components[i].widthStride
].addr
)
let dst = cast[ptr UncheckedArray[uint8]](
result.data[state.imageWidth * y].addr
)
if state.components.len == 3:
yCbCrToRgbx(
dst,
componentOutputs[0],
componentOutputs[1],
componentOutputs[2],
state.imageWidth
)
elif state.components.len == 1:
grayScaleToRgbx(
dst,
componentOutputs[0],
state.imageWidth,
)
else:
failInvalid()
proc decodeJpeg*(data: seq[uint8]): Image {.raises: [PixieError].} = proc decodeJpeg*(data: seq[uint8]): Image {.raises: [PixieError].} =
## Decodes the JPEG into an Image. ## Decodes the JPEG into an Image.
@ -1121,3 +999,6 @@ proc decodeJpeg*(data: string): Image {.inline, raises: [PixieError].} =
proc encodeJpeg*(image: Image): string = proc encodeJpeg*(image: Image): string =
raise newException(PixieError, "Encoding JPG not supported yet") raise newException(PixieError, "Encoding JPG not supported yet")
when defined(release):
{.pop.}

View file

@ -10,7 +10,7 @@ type
data*: seq[uint8] data*: seq[uint8]
UnsafeMask = object UnsafeMask = object
mask: Mask mask*: Mask
when defined(release): when defined(release):
{.push checks: off.} {.push checks: off.}

View file

@ -24,6 +24,7 @@ var files = @[
"tests/fileformats/jpeg/master/cat_4_2_2.jpg", "tests/fileformats/jpeg/master/cat_4_2_2.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0.jpg", "tests/fileformats/jpeg/master/cat_4_2_0.jpg",
"tests/fileformats/jpeg/master/cat_4_1_1.jpg", "tests/fileformats/jpeg/master/cat_4_1_1.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg",
"tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg", "tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5.jpg", "tests/fileformats/jpeg/master/cat_restart_markers_5.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg", "tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg",

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -26,6 +26,7 @@ var files = @[
"tests/fileformats/jpeg/master/cat_4_2_2.jpg", "tests/fileformats/jpeg/master/cat_4_2_2.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0.jpg", "tests/fileformats/jpeg/master/cat_4_2_0.jpg",
"tests/fileformats/jpeg/master/cat_4_1_1.jpg", "tests/fileformats/jpeg/master/cat_4_1_1.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg",
"tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg", "tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5.jpg", "tests/fileformats/jpeg/master/cat_restart_markers_5.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg", "tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg",

View file

@ -26,6 +26,7 @@ var files = @[
"tests/fileformats/jpeg/master/cat_4_2_2.jpg", "tests/fileformats/jpeg/master/cat_4_2_2.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0.jpg", "tests/fileformats/jpeg/master/cat_4_2_0.jpg",
"tests/fileformats/jpeg/master/cat_4_1_1.jpg", "tests/fileformats/jpeg/master/cat_4_1_1.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg",
"tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg", "tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5.jpg", "tests/fileformats/jpeg/master/cat_restart_markers_5.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg", "tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg",

View file

@ -1,3 +1,60 @@
import pixie/fileformats/jpg, pixie/fileformats/stb_image/stb_image import pixie, strutils, os, strformat
let original = readFile("tests/fileformats/jpeg/jpeg420exif.jpg") import pixie/fileformats/jpg
var files = @[
"tests/fileformats/jpeg/master/red.jpg",
"tests/fileformats/jpeg/master/green.jpg",
"tests/fileformats/jpeg/master/blue.jpg",
"tests/fileformats/jpeg/master/white.jpg",
"tests/fileformats/jpeg/master/black.jpg",
"tests/fileformats/jpeg/master/8x8.jpg",
"tests/fileformats/jpeg/master/8x8_progressive.jpg",
"tests/fileformats/jpeg/master/16x16.jpg",
"tests/fileformats/jpeg/master/16x16_progressive.jpg",
"tests/fileformats/jpeg/master/quality_01.jpg",
"tests/fileformats/jpeg/master/quality_10.jpg",
"tests/fileformats/jpeg/master/quality_25.jpg",
"tests/fileformats/jpeg/master/quality_50.jpg",
"tests/fileformats/jpeg/master/quality_100.jpg",
"tests/fileformats/jpeg/master/cat_4_4_4.jpg",
"tests/fileformats/jpeg/master/cat_4_4_4.jpg",
"tests/fileformats/jpeg/master/cat_4_2_2.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0.jpg",
"tests/fileformats/jpeg/master/cat_4_1_1.jpg",
"tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg",
"tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5.jpg",
"tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg",
"tests/fileformats/jpeg/master/mandrill.jpg",
"tests/fileformats/jpeg/master/exif_overrun.jpg",
"tests/fileformats/jpeg/master/grayscale_test.jpg",
"tests/fileformats/jpeg/master/progressive.jpg",
"tests/fileformats/jpeg/master/testimg.jpg",
"tests/fileformats/jpeg/master/testimgp.jpg",
"tests/fileformats/jpeg/master/testorig.jpg",
"tests/fileformats/jpeg/master/testprog.jpg",
]
for file in files:
var img = decodeJpg(readFile(file))
let genFile = file.replace("master", "generated").replace(".jpg", ".png")
let diffFile = file.replace("master", "diff").replace(".jpg", ".png")
if execShellCmd(&"convert {file} {genFile}") != 0:
echo "fail"
var img2 = readImage(genFile)
let (score, diff) = img2.diff(img)
diff.writeFile(diffFile)
if score > 1:
echo "!!!!!!!!!!!!!! FAIL !!!!!!!!!!!!!"
echo &"{score:2.3f}% ... {file}"