From 2e4efefabb00a2136b47d4c35f6e7c4e2cff974e Mon Sep 17 00:00:00 2001
From: treeform <starplant@gmail.com>
Date: Thu, 5 May 2022 09:55:25 -0700
Subject: [PATCH 1/2] Add benchmarks.

---
 tests/benchmark_jpeg.nim | 45 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 42 insertions(+), 3 deletions(-)

diff --git a/tests/benchmark_jpeg.nim b/tests/benchmark_jpeg.nim
index 3bbe13e..0558438 100644
--- a/tests/benchmark_jpeg.nim
+++ b/tests/benchmark_jpeg.nim
@@ -1,6 +1,45 @@
 import benchy, pixie/fileformats/jpg
 
-let data = readFile("tests/fileformats/jpeg/jpeg420exif.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",
 
-timeIt "pixie decode":
-  discard decodeJpg(data)
+  "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_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:
+  let data = readFile(file)
+  timeIt "jpeg " & $(len(data) div 1024) & "k decode":
+    discard decodeJpg(data)

From fdd6f51ddee2b43de664cf4302299c9e1c7b3993 Mon Sep 17 00:00:00 2001
From: treeform <starplant@gmail.com>
Date: Thu, 5 May 2022 09:55:43 -0700
Subject: [PATCH 2/2] Remove table use.

---
 src/pixie/fileformats/jpeg.nim | 58 +++++++++++++---------------------
 1 file changed, 22 insertions(+), 36 deletions(-)

diff --git a/src/pixie/fileformats/jpeg.nim b/src/pixie/fileformats/jpeg.nim
index 955f300..bc870ae 100644
--- a/src/pixie/fileformats/jpeg.nim
+++ b/src/pixie/fileformats/jpeg.nim
@@ -1,4 +1,4 @@
-import pixie/common, pixie/images, strutils, tables
+import pixie/common, pixie/images, sequtils, strutils
 
 # This JPEG decoder is loosely based on stb_image which is public domain.
 
@@ -69,6 +69,7 @@ type
     dcPred: int
     widthCoeff, heightCoeff: int
     data, coeff, lineBuf: seq[uint8]
+    blocks: seq[seq[array[64, int16]]]
 
   DecoderState = object
     buffer: seq[uint8]
@@ -86,8 +87,6 @@ type
     mcuWidth, mcuHeight, numMcuWide, numMcuHigh: int
     componentOrder: seq[int]
     progressive: bool
-    decodedBlocks: Table[(int, int, int), array[64, int16]]
-
     restartInterval: int
     todo: int
     eobRun: int
@@ -287,6 +286,14 @@ proc decodeSOF0(state: var DecoderState) =
       state.maxXScale - 1
     ) div state.maxXScale
 
+    # Allocate block data structures.
+    state.components[i].blocks = newSeqWith(
+      state.components[i].width,
+      newSeq[array[64, int16]](
+        state.components[i].height
+      )
+    )
+
     state.components[i].widthStride =
       state.numMcuWide * state.components[i].yScale * 8
     state.components[i].heightStride =
@@ -766,12 +773,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: array[64, int16]
-  if (comp, row, column) in state.decodedBlocks:
-    try:
-      data = state.decodedBlocks[(comp, row, column)]
-    except:
-      failInvalid()
+  var data = state.components[comp].blocks[row][column]
   if state.progressive:
     if state.spectralStart == 0:
       state.decodeProgressiveBlock(comp, data)
@@ -779,10 +781,7 @@ proc decodeBlock(state: var DecoderState, comp, row, column: int) =
       state.decodeProgressiveContinuationBlock(comp, data)
   else:
     state.decodeRegularBlock(comp, data)
-  try:
-    state.decodedBlocks[(comp, row, column)] = data
-  except:
-    failInvalid()
+  state.components[comp].blocks[row][column] = data
 
 template checkReset(state: var DecoderState) =
   dec state.todo
@@ -807,11 +806,8 @@ proc decodeBlocks(state: var DecoderState) =
         comp = state.componentOrder[0]
         w = (state.components[comp].width + 7) shr 3
         h = (state.components[comp].height + 7) shr 3
-      for j in 0 ..< h:
-        for i in 0 ..< w:
-          let
-            row = i * 8
-            column = j * 8
+      for column in 0 ..< h:
+        for row in 0 ..< w:
           state.decodeBlock(comp, row, column)
           state.checkReset()
     else:
@@ -822,8 +818,8 @@ proc decodeBlocks(state: var DecoderState) =
             for j in 0 ..< state.components[comp].yScale:
               for i in 0 ..< state.components[comp].xScale:
                 let
-                  row = (x * state.components[comp].xScale + i) * 8
-                  column = (y * state.components[comp].yScale + j) * 8
+                  row = (x * state.components[comp].xScale + i)
+                  column = (y * state.components[comp].yScale + j)
                 state.decodeBlock(comp, row, column)
           state.checkReset()
   else:
@@ -834,8 +830,8 @@ proc decodeBlocks(state: var DecoderState) =
           for j in 0 ..< state.components[comp].xScale:
             for i in 0 ..< state.components[comp].yScale:
               let
-                row = (x * state.components[comp].yScale + i) * 8
-                column = (y * state.components[comp].xScale + j) * 8
+                row = (x * state.components[comp].yScale + i)
+                column = (y * state.components[comp].xScale + j)
               state.decodeBlock(comp, row, column)
         state.checkReset()
 
@@ -846,19 +842,9 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
       w = (state.components[comp].width + 7) shr 3
       h = (state.components[comp].height + 7) shr 3
 
-    for j in 0 ..< h:
-      for i in 0 ..< w:
-        let
-          row = i * 8
-          column = j * 8
-
-        var data: array[64, int16]
-
-        if (comp, row, column) in state.decodedBlocks:
-          try:
-            data = state.decodedBlocks[(comp, row, column)]
-          except:
-            failInvalid()
+    for column in 0 ..< h:
+      for row in 0 ..< w:
+        var data = state.components[comp].blocks[row][column]
 
         for i in 0 ..< 64:
           let qTableId = state.components[comp].quantizationTableId
@@ -867,7 +853,7 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
           data[i] = clampInt16(data[i] * state.quantizationTables[qTableId][i].int)
 
         state.components[comp].idctBlock(
-          state.components[comp].widthStride * column + row,
+          state.components[comp].widthStride * column * 8 + row * 8,
           data
         )