From f4cd87b505a2df933522559a6126c82ddbdaf1fa Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 11:11:22 -0700 Subject: [PATCH 1/9] Simpler upscaling code. --- src/pixie/fileformats/jpeg.nim | 289 +++++++++++---------------------- 1 file changed, 96 insertions(+), 193 deletions(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index bc870ae..bebf3a4 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -1,4 +1,4 @@ -import pixie/common, pixie/images, sequtils, strutils +import pixie/common, pixie/images, pixie/masks, sequtils, strutils, chroma # This JPEG decoder is loosely based on stb_image which is public domain. @@ -68,8 +68,9 @@ type huffmanDC, huffmanAC: int dcPred: int widthCoeff, heightCoeff: int - data, coeff, lineBuf: seq[uint8] + coeff, lineBuf: seq[uint8] blocks: seq[seq[array[64, int16]]] + channel: Mask DecoderState = object buffer: seq[uint8] @@ -91,6 +92,9 @@ type todo: int eobRun: int +when defined(release): + {.push checks: off.} + template failInvalid(reason = "unable to load") = ## Throw exception with a reason. raise newException(PixieError, "Invalid JPEG, " & reason) @@ -299,8 +303,8 @@ proc decodeSOF0(state: var DecoderState) = state.components[i].heightStride = state.numMcuHigh * state.components[i].xScale * 8 - state.components[i].data.setLen( - state.components[i].widthStride * state.components[i].heightStride + state.components[i].channel = newMask( + state.components[i].widthStride, state.components[i].heightStride ) if state.progressive: @@ -760,14 +764,14 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) = x2 += 65536 + (128 shl 17) x3 += 65536 + (128 shl 17) - component.data[outPos + 0] = clampByte((x0 + t3) shr 17) - component.data[outPos + 7] = clampByte((x0 - t3) shr 17) - component.data[outPos + 1] = clampByte((x1 + t2) shr 17) - component.data[outPos + 6] = clampByte((x1 - t2) shr 17) - component.data[outPos + 2] = clampByte((x2 + t1) shr 17) - component.data[outPos + 5] = clampByte((x2 - t1) shr 17) - component.data[outPos + 3] = clampByte((x3 + t0) shr 17) - component.data[outPos + 4] = clampByte((x3 - t0) shr 17) + component.channel.data[outPos + 0] = clampByte((x0 + t3) shr 17) + component.channel.data[outPos + 7] = clampByte((x0 - t3) shr 17) + component.channel.data[outPos + 1] = clampByte((x1 + t2) shr 17) + component.channel.data[outPos + 6] = clampByte((x1 - t2) shr 17) + component.channel.data[outPos + 2] = clampByte((x2 + t1) shr 17) + component.channel.data[outPos + 5] = clampByte((x2 - t1) shr 17) + component.channel.data[outPos + 3] = clampByte((x3 + t0) shr 17) + component.channel.data[outPos + 4] = clampByte((x3 - t0) shr 17) {.pop.} @@ -857,199 +861,95 @@ proc quantizationAndIDCTPass(state: var DecoderState) = data ) -proc resampleRowH1V1( - dst, a, b: ptr UncheckedArray[uint8], - widthPreExpansion, horizontalExpansionFactor: int -): ptr UncheckedArray[uint8] = - a - -proc resampleRowH1V2( - dst, a, b: ptr UncheckedArray[uint8], - widthPreExpansion, horizontalExpansionFactor: int -): ptr UncheckedArray[uint8] = - for i in 0 ..< widthPreExpansion: - dst[i] = ((3 * a[i].int + b[i].int + 2) shr 2).uint8 - dst - -proc resampleRowH2V1( - 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) = +proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX = ## Takes a 3 component yCbCr outputs and populates image. template float2Fixed(x: float32): int = (x * 4096 + 0.5).int shl 8 - var pos: int - for i in 0 ..< width: - let - yFixed = (py[][i].int shl 20) + (1 shl 19) - cb = pcb[][i].int - 128 - cr = pcr[][i].int - 128 - var - r = yFixed + cr * float2Fixed(1.40200) - g = yFixed + - (cr * -float2Fixed(0.71414)) + - ((cb * -float2Fixed(0.34414)) and -65536) - b = yFixed + cb * float2Fixed(1.77200) - dst[pos + 0] = clampByte(r shr 20) - dst[pos + 1] = clampByte(g shr 20) - dst[pos + 2] = clampByte(b shr 20) - dst[pos + 3] = 255 - pos += 4 + let + yFixed = (py.int shl 20) + (1 shl 19) + cb = pcb.int - 128 + cr = pcr.int - 128 + var + r = yFixed + cr * float2Fixed(1.40200) + g = yFixed + + (cr * -float2Fixed(0.71414)) + + ((cb * -float2Fixed(0.34414)) and -65536) + b = yFixed + cb * float2Fixed(1.77200) + result.r = clampByte(r shr 20) + result.g = clampByte(g shr 20) + result.b = clampByte(b shr 20) + result.a = 255 -proc grayScaleToRgbx(dst, gray: ptr UncheckedArray[uint8], width: int) = +proc grayScaleToRgbx(gray: uint8): ColorRGBX = ## Takes a single gray scale component output and populates image. - var pos: int - for i in 0 ..< width: - let g = gray[i] - dst[pos + 0] = g - dst[pos + 1] = g - dst[pos + 2] = g - dst[pos + 3] = 255 - pos += 4 + let g = gray + result.r = g + result.g = g + result.b = g + result.a = 255 + +proc magnifyXBy2(mask: Mask): Mask = + result = newMask(mask.width * 2, mask.height) + for y in 0 ..< mask.height: + for x in 0 ..< mask.width: + if x == 0 or x == mask.width - 1: + result[x * 2 + 0, y] = mask[x, y] + result[x * 2 + 1, y] = mask[x, y] + else: + result[x * 2 + 0, y] = mask[x, y] div 2 + mask[x-1, y] div 2 + result[x * 2 + 1, y] = mask[x, y] div 2 + mask[x+1, y] div 2 + +proc magnifyYBy2(mask: Mask): Mask = + result = newMask(mask.width, mask.height * 2) + for y in 0 ..< mask.height: + for x in 0 ..< mask.width: + if y == 0 or y == mask.width - 1: + result[x, y * 2 + 0] = mask[x, y] + result[x, y * 2 + 1] = mask[x, y] + else: + result[x, y * 2 + 0] = mask[x, y] div 2 + mask[x, y-1] div 2 + result[x, y * 2 + 1] = mask[x, y] div 2 + mask[x, y+1] div 2 proc buildImage(state: var DecoderState): Image = ## Takes a jpeg image object and builds a pixie Image from it. + + if state.components.len == 3: + for componentIdx, component in state.components.mpairs: + + while component.xScale < state.maxXScale: + component.channel = component.channel.magnifyYBy2() + component.xScale *= 2 + + while component.yScale < state.maxYScale: + component.channel = component.channel.magnifyXBy2() + component.yScale *= 2 + result = newImage(state.imageWidth, state.imageHeight) - var resamples: array[3, Resample] - for i in 0 ..< state.components.len: - resamples[i].horizontalExpansionFactor = - state.maxYScale div - state.components[i].yScale - resamples[i].verticalExpansionFactor = - state.maxXScale div - state.components[i].xScale - resamples[i].yStep = resamples[i].verticalExpansionFactor shr 1 - resamples[i].widthPreExpansion = ( - state.imageWidth + resamples[i].horizontalExpansionFactor - 1 - ) div resamples[i].horizontalExpansionFactor + if state.components.len == 3: + let + cy = state.components[0].channel + cb = state.components[1].channel + cr = state.components[2].channel + for y in 0 ..< state.imageHeight: + for x in 0 ..< state.imageWidth: + result[x, y] = yCbCrToRgbx( + cy[x, y], + cb[x, y], + cr[x, y], + ) - resamples[i].line0 = cast[ptr UncheckedArray[uint8]]( - state.components[i].data[0].addr - ) - resamples[i].line1 = cast[ptr UncheckedArray[uint8]]( - state.components[i].data[0].addr - ) - state.components[i].lineBuf.setLen(state.imageWidth + 3) - - if resamples[i].horizontalExpansionFactor == 1 and - resamples[i].verticalExpansionFactor == 1: - 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() + elif state.components.len == 1: + let + cy = state.components[0].channel + for y in 0 ..< state.imageHeight: + for x in 0 ..< state.imageWidth: + result[x, y] = grayScaleToRgbx( + cy[x, y], + ) + else: + failInvalid() proc decodeJpeg*(data: seq[uint8]): Image {.raises: [PixieError].} = ## Decodes the JPEG into an Image. @@ -1121,3 +1021,6 @@ proc decodeJpeg*(data: string): Image {.inline, raises: [PixieError].} = proc encodeJpeg*(image: Image): string = raise newException(PixieError, "Encoding JPG not supported yet") + +when defined(release): + {.pop.} From bc635bc3da95c9460c6ac874dd6a23b53a7aefe5 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 11:18:58 -0700 Subject: [PATCH 2/9] clean up --- src/pixie/fileformats/jpeg.nim | 67 ++++++++++++++++------------------ 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index bebf3a4..ef11be1 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -103,10 +103,6 @@ template clampByte(x): uint8 = ## Clamp integer into byte range. 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 = ## Reads a byte from the input stream. if state.pos >= state.buffer.len: @@ -243,8 +239,8 @@ proc decodeSOF0(state: var DecoderState) = failInvalid("unsupported component count, must be 1 or 3") for i in 0 ..< numComponents: - state.components.add(Component()) - state.components[i].id = state.readUint8() + var component = Component() + component.id = state.readUint8() let info = state.readUint8() vertical = info and 15 @@ -257,18 +253,19 @@ proc decodeSOF0(state: var DecoderState) = if vertical == 0 or vertical > 4 or horizontal == 0 or horizontal > 4: failInvalid("invalid component scaling factor") - state.components[i].xScale = vertical.int - state.components[i].yScale = horizontal.int - state.components[i].quantizationTableId = quantizationTableId + component.xScale = vertical.int + component.yScale = horizontal.int + 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, - state.components[i].xScale + component.xScale ) state.maxYScale = max( state.maxYScale, - state.components[i].yScale + component.yScale ) state.mcuWidth = state.maxYScale * 8 @@ -278,40 +275,40 @@ proc decodeSOF0(state: var DecoderState) = state.numMcuHigh = (state.imageHeight + state.mcuHeight - 1) div state.mcuHeight - for i in 0 ..< state.components.len: - state.components[i].width = ( + for component in state.components.mitems: + component.width = ( state.imageWidth * - state.components[i].yScale + + component.yScale + state.maxYScale - 1 ) div state.maxYScale - state.components[i].height = ( + component.height = ( state.imageHeight * - state.components[i].xScale + + component.xScale + state.maxXScale - 1 ) div state.maxXScale # Allocate block data structures. - state.components[i].blocks = newSeqWith( - state.components[i].width, + component.blocks = newSeqWith( + component.width, newSeq[array[64, int16]]( - state.components[i].height + component.height ) ) - state.components[i].widthStride = - state.numMcuWide * state.components[i].yScale * 8 - state.components[i].heightStride = - state.numMcuHigh * state.components[i].xScale * 8 + component.widthStride = + state.numMcuWide * component.yScale * 8 + component.heightStride = + state.numMcuHigh * component.xScale * 8 - state.components[i].channel = newMask( - state.components[i].widthStride, state.components[i].heightStride + component.channel = newMask( + component.widthStride, component.heightStride ) if state.progressive: - state.components[i].widthCoeff = state.components[i].widthStride div 8 - state.components[i].heightCoeff = state.components[i].heightStride div 8 - state.components[i].coeff.setLen( - state.components[i].widthStride * state.components[i].heightStride + component.widthCoeff = component.widthStride div 8 + component.heightCoeff = component.heightStride div 8 + component.coeff.setLen( + component.widthStride * component.heightStride ) proc decodeSOF1(state: var DecoderState) = @@ -509,7 +506,7 @@ proc decodeRegularBlock( state.getBitsAsSignedInt(t) dc = state.components[component].dcPred + diff state.components[component].dcPred = dc - data[0] = clampInt16(dc) + data[0] = dc.int16 var i = 1 while true: @@ -551,7 +548,7 @@ proc decodeProgressiveBlock( let dc = state.components[component].dcPred + diff state.components[component].dcPred = dc - data[0] = clampInt16(dc * (1 shl state.successiveApproxLow)) + data[0] = (dc * (1 shl state.successiveApproxLow)).int16 else: if getBit(state) != 0: @@ -596,7 +593,7 @@ proc decodeProgressiveContinuationBlock( inc k if s >= 15: 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): break @@ -708,7 +705,7 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) = data[i + 40] == 0 and data[i + 48] == 0 and data[i + 56] == 0: - let dcterm = clampInt16(data[i].int * 4.int) + let dcterm = data[i] * 4 values[i + 0] = dcterm values[i + 8] = dcterm values[i + 16] = dcterm @@ -854,7 +851,7 @@ proc quantizationAndIDCTPass(state: var DecoderState) = let qTableId = state.components[comp].quantizationTableId if qTableId.int notin 0 ..< state.quantizationTables.len: 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].widthStride * column * 8 + row * 8, From d1780825e3f91a8acac8843ae0b5a78ce52ddd11 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 11:20:05 -0700 Subject: [PATCH 3/9] Ignore int16 overflows. --- src/pixie/fileformats/jpeg.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index ef11be1..9651439 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -770,8 +770,6 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) = component.channel.data[outPos + 3] = 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) = ## Decodes a block. var data = state.components[comp].blocks[row][column] @@ -908,6 +906,8 @@ proc magnifyYBy2(mask: Mask): Mask = result[x, y * 2 + 0] = mask[x, y] div 2 + mask[x, y-1] div 2 result[x, y * 2 + 1] = mask[x, y] div 2 + mask[x, y+1] div 2 +{.pop.} + proc buildImage(state: var DecoderState): Image = ## Takes a jpeg image object and builds a pixie Image from it. From 9c9645598eac73bf044fe8be6a8c7458612a2b16 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 11:24:54 -0700 Subject: [PATCH 4/9] Fuzzing. --- src/pixie/fileformats/jpeg.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index 9651439..5610239 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -489,7 +489,7 @@ proc getBitsAsUnsignedInt(state: var DecoderState, n: int): int = state.bitCount -= n return k.int -{.push overflowChecks: off.} +{.push overflowChecks: off, rangeChecks: off.} proc decodeRegularBlock( state: var DecoderState, component: int, data: var array[64, int16] From a2b6540f89b71e3180c3e37863dada13220fbcfd Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 11:44:46 -0700 Subject: [PATCH 5/9] {.byaddr.} 2% speed up. --- src/pixie/fileformats/jpeg.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index 5610239..cdb3020 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -1,4 +1,4 @@ -import pixie/common, pixie/images, pixie/masks, sequtils, strutils, chroma +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. @@ -658,6 +658,7 @@ proc decodeProgressiveContinuationBlock( break 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 fsh(x: int32): int32 = x * 4096 p2 = s2 @@ -696,6 +697,7 @@ template idct1D(s0, s1, s2, s3, s4, s5, s6, s7: int32) = t0 += p1 + p3 proc idctBlock(component: var Component, offset: int, data: array[64, int16]) = + ## Inverse discrete cosine transform whole block. var values: array[64, int32] for i in 0 ..< 8: if data[i + 8] == 0 and @@ -772,7 +774,7 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) = proc decodeBlock(state: var DecoderState, comp, row, column: int) = ## 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.spectralStart == 0: state.decodeProgressiveBlock(comp, data) @@ -780,9 +782,9 @@ proc decodeBlock(state: var DecoderState, comp, row, column: int) = state.decodeProgressiveContinuationBlock(comp, data) else: state.decodeRegularBlock(comp, data) - state.components[comp].blocks[row][column] = data template checkReset(state: var DecoderState) = + ## Check if we might have run into a reset marker, then deal with it. dec state.todo if state.todo <= 0: if state.bitCount < 24: @@ -793,7 +795,6 @@ template checkReset(state: var DecoderState) = state.pos += 2 else: failInvalid("did not get expected reset marker") - state.reset() proc decodeBlocks(state: var DecoderState) = From a6ea5fe6c2fc05057f5769757eed6aaa5f58a935 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 12:03:03 -0700 Subject: [PATCH 6/9] Simplfy component ordering. --- src/pixie/fileformats/jpeg.nim | 50 +++++++----------- .../jpeg/master/cat_4_2_0_progressive.jpg | Bin 0 -> 19027 bytes 2 files changed, 19 insertions(+), 31 deletions(-) create mode 100644 tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index cdb3020..edca7e2 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -344,7 +344,7 @@ proc decodeSOS(state: var DecoderState) = if state.scanComponents notin [1, 3]: failInvalid("unsupported scan component count") - state.componentOrder = @[] + state.componentOrder.setLen(0) for i in 0 ..< state.scanComponents: let @@ -799,40 +799,28 @@ template checkReset(state: var DecoderState) = proc decodeBlocks(state: var DecoderState) = ## Decodes scan data blocks that follow a SOS block. - if state.progressive: - if state.scanComponents == 1: - # Single component pass. - let - comp = state.componentOrder[0] - w = (state.components[comp].width + 7) shr 3 - h = (state.components[comp].height + 7) shr 3 - for column in 0 ..< h: - for row in 0 ..< w: - state.decodeBlock(comp, row, column) - 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() + if state.scanComponents == 1: + # Single component pass. + + let + comp = state.componentOrder[0] + w = (state.components[comp].width + 7) shr 3 + h = (state.components[comp].height + 7) shr 3 + for column in 0 ..< h: + for row in 0 ..< w: + state.decodeBlock(comp, row, column) + state.checkReset() else: # Interleaved regular component pass. - for y in 0 ..< state.numMcuHigh: - for x in 0 ..< state.numMcuWide: + for mcuY in 0 ..< state.numMcuHigh: + for mcuX in 0 ..< state.numMcuWide: for comp in state.componentOrder: - for j in 0 ..< state.components[comp].xScale: - for i in 0 ..< state.components[comp].yScale: + for compY in 0 ..< state.components[comp].xScale: + for compX in 0 ..< state.components[comp].yScale: let - row = (x * state.components[comp].yScale + i) - column = (y * state.components[comp].xScale + j) - state.decodeBlock(comp, row, column) + row = (mcuX * state.components[comp].yScale + compX) + col = (mcuY * state.components[comp].xScale + compY) + state.decodeBlock(comp, row, col) state.checkReset() proc quantizationAndIDCTPass(state: var DecoderState) = diff --git a/tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg b/tests/fileformats/jpeg/master/cat_4_2_0_progressive.jpg new file mode 100644 index 0000000000000000000000000000000000000000..72ecc4aac94de04aac7d9eddadf2e463b49549ea GIT binary patch literal 19027 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 literal 0 HcmV?d00001 From 1825ab6cd755a3eb11a92dc0cb3891ab8cfad319 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 12:06:18 -0700 Subject: [PATCH 7/9] Cleanup --- src/pixie/fileformats/jpeg.nim | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index edca7e2..343a422 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -50,16 +50,6 @@ type maxCodes: array[18, int] 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 id, quantizationTableId: uint8 yScale, xScale: int @@ -151,6 +141,7 @@ proc decodeDQT(state: var DecoderState) = failInvalid("DQT table length did not match") proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) = + ## Builds the huffman data structure. block: var k: int for i in 0.uint8 ..< 16: @@ -187,7 +178,6 @@ proc buildHuffman(huffman: var Huffman, counts: array[16, uint8]) = proc decodeDHT(state: var DecoderState) = ## Decode Define Huffman Table - var len = state.readUint16be() - 2 while len > 0: let @@ -259,14 +249,8 @@ proc decodeSOF0(state: var DecoderState) = state.components.add(component) for component in state.components.mitems: - state.maxXScale = max( - state.maxXScale, - component.xScale - ) - state.maxYScale = max( - state.maxYScale, - component.yScale - ) + state.maxXScale = max(state.maxXScale, component.xScale) + state.maxYScale = max(state.maxYScale, component.yScale) state.mcuWidth = state.maxYScale * 8 state.mcuHeight = state.maxXScale * 8 @@ -295,10 +279,8 @@ proc decodeSOF0(state: var DecoderState) = ) ) - component.widthStride = - state.numMcuWide * component.yScale * 8 - component.heightStride = - state.numMcuHigh * component.xScale * 8 + component.widthStride = state.numMcuWide * component.yScale * 8 + component.heightStride = state.numMcuHigh * component.xScale * 8 component.channel = newMask( component.widthStride, component.heightStride @@ -321,6 +303,7 @@ proc decodeSOF2(state: var DecoderState) = state.progressive = true proc reset(state: var DecoderState) = + ## Rests the decoder state need for reset markers. state.bits = 0 state.bitCount = 0 for component in 0 ..< state.components.len: From ecd59d214a2be1163cd1260390e7b07c2f6f32c8 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 12:19:15 -0700 Subject: [PATCH 8/9] Better JPEG validation. --- .gitignore | 1 + tests/benchmark_jpeg.nim | 1 + tests/fuzz_jpeg.nim | 1 + tests/test_jpeg.nim | 1 + tests/validate_jpeg.nim | 61 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c85394a..64c9d74 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bindings/generated *.dSYM dump.txt tests/fileformats/jpeg/generated +tests/fileformats/jpeg/diff diff --git a/tests/benchmark_jpeg.nim b/tests/benchmark_jpeg.nim index 0558438..498e691 100644 --- a/tests/benchmark_jpeg.nim +++ b/tests/benchmark_jpeg.nim @@ -24,6 +24,7 @@ var files = @[ "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", diff --git a/tests/fuzz_jpeg.nim b/tests/fuzz_jpeg.nim index 3f9d7a8..248ab56 100644 --- a/tests/fuzz_jpeg.nim +++ b/tests/fuzz_jpeg.nim @@ -26,6 +26,7 @@ var files = @[ "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", diff --git a/tests/test_jpeg.nim b/tests/test_jpeg.nim index de65564..93b8e45 100644 --- a/tests/test_jpeg.nim +++ b/tests/test_jpeg.nim @@ -26,6 +26,7 @@ var files = @[ "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", diff --git a/tests/validate_jpeg.nim b/tests/validate_jpeg.nim index b37d018..f3f5647 100644 --- a/tests/validate_jpeg.nim +++ b/tests/validate_jpeg.nim @@ -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}" From aec2c23253fb68e133108b816e98e3d38b46c1bf Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 7 May 2022 17:14:29 -0700 Subject: [PATCH 9/9] Better magnify. --- src/pixie/fileformats/jpeg.nim | 75 +++++++++++++++++++--------------- src/pixie/masks.nim | 2 +- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim index 343a422..baa22dd 100644 --- a/src/pixie/fileformats/jpeg.nim +++ b/src/pixie/fileformats/jpeg.nim @@ -828,6 +828,38 @@ proc quantizationAndIDCTPass(state: var DecoderState) = data ) +proc magnifyXBy2(mask: Mask): Mask = + ## Smooth magnify by power of 2 only in the X direction. + result = newMask(mask.width * 2, mask.height) + for y in 0 ..< mask.height: + 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 magnifyYBy2(mask: Mask): Mask = + ## Smooth magnify by power of 2 only in the Y direction. + result = newMask(mask.width, mask.height * 2) + for y in 0 ..< mask.height: + for x in 0 ..< mask.width: + let n = 3 * mask.unsafe[x, y].uint16 + 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 yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX = ## Takes a 3 component yCbCr outputs and populates image. template float2Fixed(x: float32): int = @@ -856,44 +888,21 @@ proc grayScaleToRgbx(gray: uint8): ColorRGBX = result.b = g result.a = 255 -proc magnifyXBy2(mask: Mask): Mask = - result = newMask(mask.width * 2, mask.height) - for y in 0 ..< mask.height: - for x in 0 ..< mask.width: - if x == 0 or x == mask.width - 1: - result[x * 2 + 0, y] = mask[x, y] - result[x * 2 + 1, y] = mask[x, y] - else: - result[x * 2 + 0, y] = mask[x, y] div 2 + mask[x-1, y] div 2 - result[x * 2 + 1, y] = mask[x, y] div 2 + mask[x+1, y] div 2 - -proc magnifyYBy2(mask: Mask): Mask = - result = newMask(mask.width, mask.height * 2) - for y in 0 ..< mask.height: - for x in 0 ..< mask.width: - if y == 0 or y == mask.width - 1: - result[x, y * 2 + 0] = mask[x, y] - result[x, y * 2 + 1] = mask[x, y] - else: - result[x, y * 2 + 0] = mask[x, y] div 2 + mask[x, y-1] div 2 - result[x, y * 2 + 1] = mask[x, y] div 2 + mask[x, y+1] div 2 - {.pop.} proc buildImage(state: var DecoderState): Image = ## Takes a jpeg image object and builds a pixie Image from it. if state.components.len == 3: - for componentIdx, component in state.components.mpairs: + 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 - while component.yScale < state.maxYScale: - component.channel = component.channel.magnifyXBy2() - component.yScale *= 2 - result = newImage(state.imageWidth, state.imageHeight) if state.components.len == 3: @@ -903,10 +912,10 @@ proc buildImage(state: var DecoderState): Image = cr = state.components[2].channel for y in 0 ..< state.imageHeight: for x in 0 ..< state.imageWidth: - result[x, y] = yCbCrToRgbx( - cy[x, y], - cb[x, y], - cr[x, y], + result.unsafe[x, y] = yCbCrToRgbx( + cy.unsafe[x, y], + cb.unsafe[x, y], + cr.unsafe[x, y], ) elif state.components.len == 1: @@ -914,8 +923,8 @@ proc buildImage(state: var DecoderState): Image = cy = state.components[0].channel for y in 0 ..< state.imageHeight: for x in 0 ..< state.imageWidth: - result[x, y] = grayScaleToRgbx( - cy[x, y], + result.unsafe[x, y] = grayScaleToRgbx( + cy.unsafe[x, y], ) else: failInvalid() diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim index 3bf80be..4ee0059 100644 --- a/src/pixie/masks.nim +++ b/src/pixie/masks.nim @@ -10,7 +10,7 @@ type data*: seq[uint8] UnsafeMask = object - mask: Mask + mask*: Mask when defined(release): {.push checks: off.}