From a4440a1c6bf96707caa2bcc5e45a1b49c40d794d Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Sat, 1 Feb 2020 11:42:22 +0100
Subject: [PATCH] Avoid re-calculating the `xScaleBlockOffset` when not
 necessary in `JpegImage._getLinearizedBlockData`

As can be seen in the code, the `xScaleBlockOffset` typed array doesn't depend on the actual image data but only on the width and x-scale. The width is obviously consistent for an image, and it turns out that in practice the `componentScaleX` is quite often identical between two (or more) adjacent image components.
All-in-all it's thus not necessary to *unconditionally* re-compute the `xScaleBlockOffset` when getting the JPEG image data.

While avoiding, in many cases, one or more loops can never be a bad thing these changes are unfortunately completely dominated by the rest of the JpegImage code and consequently doesn't really show up in benchmark results. *Hence I'd understand if this patch is ultimately deemed not necessary.*
---
 src/core/jpg.js | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/core/jpg.js b/src/core/jpg.js
index 5f973ba6e..9abc8de80 100644
--- a/src/core/jpg.js
+++ b/src/core/jpg.js
@@ -1105,6 +1105,7 @@ var JpegImage = (function JpegImageClosure() {
       var data = new Uint8ClampedArray(dataLength);
       var xScaleBlockOffset = new Uint32Array(width);
       var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs
+      let lastComponentScaleX;
 
       for (i = 0; i < numComponents; i++) {
         component = this.components[i];
@@ -1113,10 +1114,14 @@ var JpegImage = (function JpegImageClosure() {
         offset = i;
         output = component.output;
         blocksPerScanline = (component.blocksPerLine + 1) << 3;
-        // precalculate the xScaleBlockOffset
-        for (x = 0; x < width; x++) {
-          j = 0 | (x * componentScaleX);
-          xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7);
+        // Precalculate the `xScaleBlockOffset`. Since it doesn't depend on the
+        // component data, that's only necessary when `componentScaleX` changes.
+        if (componentScaleX !== lastComponentScaleX) {
+          for (x = 0; x < width; x++) {
+            j = 0 | (x * componentScaleX);
+            xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7);
+          }
+          lastComponentScaleX = componentScaleX;
         }
         // linearize the blocks of the component
         for (y = 0; y < height; y++) {