percippet

Benchmark created by shoniko on


Preparation HTML

<img src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png" id="sourceImage" crossOrigin="Anonymous"></img>

Setup

let sourceImage = document.getElementById("sourceImage");
  let canvas = document.createElement("canvas");
  let context = canvas.getContext("2d");
  let width = sourceImage.width;
  let height = sourceImage.height;
  canvas.width = width;
  canvas.height = height;
  context.drawImage(
            sourceImage, 0, 0, width, height, 0, 0, width, height);
  let imageData = context.getImageData(0, 0, width, height);
  
  
  function hashImageBeforeOptimization(imageData, blockBits)
  {
    function median(data)
    {
      let mdarr = data.slice(0);
      mdarr.sort((a, b) => a - b);
      if (mdarr.length % 2 === 0)
      {
        return (mdarr[mdarr.length / 2 - 1] + mdarr[mdarr.length / 2]) / 2.0;
      }
      return mdarr[Math.floor(mdarr.length / 2)];
    }
  
    function translateBlocksToBits(blocks, pixelsPerBlock)
    {
      let halfBlockValue = pixelsPerBlock * 256 * 3 / 2;
      let bandsize = blocks.length / 4;
  
      // Compare medians across four horizontal bands
      for (let i = 0; i < 4; i++)
      {
        let m = median(blocks.slice(i * bandsize, (i + 1) * bandsize));
        for (let j = i * bandsize; j < (i + 1) * bandsize; j++)
        {
          let v = blocks[j];
  
          // Output a 1 if the block is brighter than the median.
          // With images dominated by black or white, the median may
          // end up being 0 or the max value, and thus having a lot
          // of blocks of value equal to the median.  To avoid
          // generating hashes of all zeros or ones, in that case output
          // 0 if the median is in the lower value space, 1 otherwise
          blocks[j] = Number(v > m ||
            (Math.abs(v - m) < 1 && m > halfBlockValue));
        }
      }
    }
  
    function bitsToHexhash(bitsArray)
    {
      let hex = [];
      for (let i = 0; i < bitsArray.length; i += 4)
      {
        let nibble = bitsArray.slice(i, i + 4);
        hex.push(parseInt(nibble.join(""), 2).toString(16));
      }
  
      return hex.join("");
    }
  
    function bmvbhashEven(data, bits)
    {
      let blocksizeX = Math.floor(data.width / bits);
      let blocksizeY = Math.floor(data.height / bits);
  
      let result = [];
  
      for (let y = 0; y < bits; y++)
      {
        for (let x = 0; x < bits; x++)
        {
          let total = 0;
  
          for (let iy = 0; iy < blocksizeY; iy++)
          {
            for (let ix = 0; ix < blocksizeX; ix++)
            {
              let cx = x * blocksizeX + ix;
              let cy = y * blocksizeY + iy;
              let ii = (cy * data.width + cx) * 4;
  
              let alpha = data.data[ii + 3];
              if (alpha === 0)
              {
                total += 765;
              }
              else
              {
                total += data.data[ii] + data.data[ii + 1] + data.data[ii + 2];
              }
            }
          }
  
          result.push(total);
        }
      }
  
      translateBlocksToBits(result, blocksizeX * blocksizeY);
      return bitsToHexhash(result);
    }
  
    function bmvbhash(data, bits)
    {
      let result = [];
  
      let i; let j; let x; let y;
      let blockWidth; let blockHeight;
      let weightTop; let weightBottom; let weightLeft; let weightRight;
      let blockTop; let blockBottom; let blockLeft; let blockRight;
      let yMod; let yFrac; let yInt;
      let xMod; let xFrac; let xInt;
      let blocks = [];
  
      let evenX = data.width % bits === 0;
      let evenY = data.height % bits === 0;
  
      if (evenX && evenY)
      {
        return bmvbhashEven(data, bits);
      }
  
      // initialize blocks array with 0s
      for (i = 0; i < bits; i++)
      {
        blocks.push([]);
        for (j = 0; j < bits; j++)
        {
          blocks[i].push(0);
        }
      }
  
      blockWidth = data.width / bits;
      blockHeight = data.height / bits;
  
      for (y = 0; y < data.height; y++)
      {
        if (evenY)
        {
          // don't bother dividing y, if the size evenly divides by bits
          blockTop = blockBottom = Math.floor(y / blockHeight);
          weightTop = 1;
          weightBottom = 0;
        }
        else
        {
          yMod = (y + 1) % blockHeight;
          yFrac = yMod - Math.floor(yMod);
          yInt = yMod - yFrac;
  
          weightTop = (1 - yFrac);
          weightBottom = (yFrac);
  
          // yInt will be 0 on bottom/right borders and on block boundaries
          if (yInt > 0 || (y + 1) === data.height)
          {
            blockTop = blockBottom = Math.floor(y / blockHeight);
          }
          else
          {
            blockTop = Math.floor(y / blockHeight);
            blockBottom = Math.ceil(y / blockHeight);
          }
        }
  
        for (x = 0; x < data.width; x++)
        {
          let ii = (y * data.width + x) * 4;
  
          let avgvalue; let alpha = data.data[ii + 3];
          if (alpha === 0)
          {
            avgvalue = 765;
          }
          else
          {
            avgvalue = data.data[ii] + data.data[ii + 1] + data.data[ii + 2];
          }
  
          if (evenX)
          {
            blockLeft = blockRight = Math.floor(x / blockWidth);
            weightLeft = 1;
            weightRight = 0;
          }
          else
          {
            xMod = (x + 1) % blockWidth;
            xFrac = xMod - Math.floor(xMod);
            xInt = xMod - xFrac;
  
            weightLeft = (1 - xFrac);
            weightRight = xFrac;
  
            // xInt will be 0 on bottom/right borders and on block boundaries
            if (xInt > 0 || (x + 1) === data.width)
            {
              blockLeft = blockRight = Math.floor(x / blockWidth);
            }
            else
            {
              blockLeft = Math.floor(x / blockWidth);
              blockRight = Math.ceil(x / blockWidth);
            }
          }
  
          // add weighted pixel value to relevant blocks
          blocks[blockTop][blockLeft] += avgvalue * weightTop * weightLeft;
          blocks[blockTop][blockRight] += avgvalue * weightTop * weightRight;
          blocks[blockBottom][blockLeft] += avgvalue * weightBottom * weightLeft;
          blocks[blockBottom][blockRight] +=
            avgvalue * weightBottom * weightRight;
        }
      }
  
      for (i = 0; i < bits; i++)
      {
        for (j = 0; j < bits; j++)
        {
          result.push(blocks[i][j]);
        }
      }
  
      translateBlocksToBits(result, blockWidth * blockHeight);
      return bitsToHexhash(result);
    }
  
    return bmvbhash(imageData, blockBits);
  }
  
  
  
  function hashImageIssue55(imageData, blockBits)
  {
    function median(data)
    {
      let mdarr = data.slice(0);
      mdarr.sort((a, b) => a - b);
      if (mdarr.length % 2 === 0)
      {
        return (mdarr[mdarr.length / 2 - 1] + mdarr[mdarr.length / 2]) / 2.0;
      }
      return mdarr[Math.floor(mdarr.length / 2)];
    }
  
    function translateBlocksToBits(blocks, pixelsPerBlock)
    {
      let halfBlockValue = pixelsPerBlock * 256 * 3 / 2;
      let bandsize = blocks.length / 4;
  
      // Compare medians across four horizontal bands
      for (let i = 0; i < 4; i++)
      {
        let m = median(blocks.slice(i * bandsize, (i + 1) * bandsize));
        for (let j = i * bandsize; j < (i + 1) * bandsize; j++)
        {
          let v = blocks[j];
  
          // Output a 1 if the block is brighter than the median.
          // With images dominated by black or white, the median may
          // end up being 0 or the max value, and thus having a lot
          // of blocks of value equal to the median.  To avoid
          // generating hashes of all zeros or ones, in that case output
          // 0 if the median is in the lower value space, 1 otherwise
          blocks[j] = Number(v > m ||
            (Math.abs(v - m) < 1 && m > halfBlockValue));
        }
      }
    }
  
    function bitsToHexhash(bitsArray)
    {
      let hex = [];
      for (let i = 0; i < bitsArray.length; i += 4)
      {
        let nibble = bitsArray.slice(i, i + 4);
        hex.push(parseInt(nibble.join(""), 2).toString(16));
      }
  
      return hex.join("");
    }
  
    function bmvbhashEven(data, bits)
    {
      let blocksizeX = Math.floor(data.width / bits);
      let blocksizeY = Math.floor(data.height / bits);
  
      let result = [];
  
      let imgData = data.data;
      let dataWidth = data.width;
  
      for (let y = 0; y < bits; y++)
      {
        for (let x = 0; x < bits; x++)
        {
          let total = 0;
  
          for (let iy = 0; iy < blocksizeY; iy++)
          {
            for (let ix = 0; ix < blocksizeX; ix++)
            {
              let cx = x * blocksizeX + ix;
              let cy = y * blocksizeY + iy;
              let ii = (cy * dataWidth + cx) * 4;
  
              let alpha = imgData[ii + 3];
              if (alpha === 0)
              {
                total += 765;
              }
              else
              {
                total += imgData[ii] + imgData[ii + 1] + imgData[ii + 2];
              }
            }
          }
  
          result.push(total);
        }
      }
  
      translateBlocksToBits(result, blocksizeX * blocksizeY);
      return bitsToHexhash(result);
    }
  
    function bmvbhash(data, bits)
    {
      let result = [];
  
      let i; let j; let x; let y;
      let blockWidth; let blockHeight;
      let weightTop; let weightBottom; let weightLeft; let weightRight;
      let blockTop; let blockBottom; let blockLeft; let blockRight;
      let yMod; let yFrac; let yInt;
      let xMod; let xFrac; let xInt;
      let blocks = [];
  
      let evenX = data.width % bits === 0;
      let evenY = data.height % bits === 0;
  
      if (evenX && evenY)
      {
        return bmvbhashEven(data, bits);
      }
  
      // initialize blocks array with 0s
      for (i = 0; i < bits; i++)
      {
        blocks.push([]);
        for (j = 0; j < bits; j++)
        {
          blocks[i].push(0);
        }
      }
  
      let imgData = data.data;
      let dataWidth = data.width;
      let dataHeight = data.height;
  
      blockWidth = dataWidth / bits;
      blockHeight = dataHeight / bits;
  
      for (y = 0; y < dataHeight; y++)
      {
        if (evenY)
        {
          // don't bother dividing y, if the size evenly divides by bits
          blockTop = blockBottom = Math.floor(y / blockHeight);
          weightTop = 1;
          weightBottom = 0;
        }
        else
        {
          yMod = (y + 1) % blockHeight;
          yFrac = yMod - Math.floor(yMod);
          yInt = yMod - yFrac;
  
          weightTop = (1 - yFrac);
          weightBottom = (yFrac);
  
          // yInt will be 0 on bottom/right borders and on block boundaries
          if (yInt > 0 || (y + 1) === dataHeight)
          {
            blockTop = blockBottom = Math.floor(y / blockHeight);
          }
          else
          {
            blockTop = Math.floor(y / blockHeight);
            blockBottom = Math.ceil(y / blockHeight);
          }
        }
  
        for (x = 0; x < dataWidth; x++)
        {
          let ii = (y * dataWidth + x) * 4;
  
          let avgvalue; let alpha = imgData[ii + 3];
          if (alpha === 0)
          {
            avgvalue = 765;
          }
          else
          {
            avgvalue = imgData[ii] + imgData[ii + 1] + imgData[ii + 2];
          }
  
          if (evenX)
          {
            blockLeft = blockRight = Math.floor(x / blockWidth);
            weightLeft = 1;
            weightRight = 0;
          }
          else
          {
            xMod = (x + 1) % blockWidth;
            xFrac = xMod - Math.floor(xMod);
            xInt = xMod - xFrac;
  
            weightLeft = (1 - xFrac);
            weightRight = xFrac;
  
            // xInt will be 0 on bottom/right borders and on block boundaries
            if (xInt > 0 || (x + 1) === dataWidth)
            {
              blockLeft = blockRight = Math.floor(x / blockWidth);
            }
            else
            {
              blockLeft = Math.floor(x / blockWidth);
              blockRight = Math.ceil(x / blockWidth);
            }
          }
  
          // add weighted pixel value to relevant blocks
          blocks[blockTop][blockLeft] += avgvalue * weightTop * weightLeft;
          blocks[blockTop][blockRight] += avgvalue * weightTop * weightRight;
          blocks[blockBottom][blockLeft] += avgvalue * weightBottom * weightLeft;
          blocks[blockBottom][blockRight] +=
            avgvalue * weightBottom * weightRight;
        }
      }
  
      for (i = 0; i < bits; i++)
      {
        for (j = 0; j < bits; j++)
        {
          result.push(blocks[i][j]);
        }
      }
  
      translateBlocksToBits(result, blockWidth * blockHeight);
      return bitsToHexhash(result);
    }
  
    return bmvbhash(imageData, blockBits);
  }
  
  function hashImageIssue62(imageData, blockBits)
  {
    function median(mdarr)
    {
      mdarr.sort((a, b) => a - b);
      let {length} = mdarr;
      if (length % 2 === 0)
      {
        return (mdarr[length / 2 - 1] + mdarr[length / 2]) / 2.0;
      }
      return mdarr[Math.floor(length / 2)];
    }
  
    function translateBlocksToBits(blocks, pixelsPerBlock)
    {
      let halfBlockValue = pixelsPerBlock * 256 * 3 / 2;
      let bandsize = blocks.length / 4;
  
      // Compare medians across four horizontal bands
      for (let i = 0; i < 4; i++)
      {
        let index = i * bandsize;
        let length = (i + 1) * bandsize;
        let m = median(blocks.slice(index, length));
        for (let j = index; j < length; j++)
        {
          let v = blocks[j];
  
          // Output a 1 if the block is brighter than the median.
          // With images dominated by black or white, the median may
          // end up being 0 or the max value, and thus having a lot
          // of blocks of value equal to the median.  To avoid
          // generating hashes of all zeros or ones, in that case output
          // 0 if the median is in the lower value space, 1 otherwise
          blocks[j] = (v > m ||
            (Math.abs(v - m) < 1 && m > halfBlockValue)) ? 1 : 0;
        }
      }
    }
  
    function bitsToHexhash(bitsArray)
    {
      let hex = [];
      let {length} = bitsArray;
      for (let i = 0; i < length; i += 4)
      {
        let nibble = bitsArray.slice(i, i + 4);
        hex.push(parseInt(nibble.join(""), 2).toString(16));
      }
  
      return hex.join("");
    }
  
    function bmvbhashEven(data, bits)
    {
      let {width, height, data: imageData} = data;
      let blocksizeX = Math.floor(width / bits);
      let blocksizeY = Math.floor(height / bits);
  
      let result = [];
  
      for (let y = 0; y < bits; y++)
      {
        for (let x = 0; x < bits; x++)
        {
          let total = 0;
  
          for (let iy = 0; iy < blocksizeY; iy++)
          {
            for (let ix = 0; ix < blocksizeX; ix++)
            {
              let cx = x * blocksizeX + ix;
              let cy = y * blocksizeY + iy;
              let ii = (cy * width + cx) * 4;
  
              let alpha = imageData[ii + 3];
              if (alpha === 0)
              {
                total += 765;
              }
              else
              {
                total += imageData[ii] + imageData[ii + 1] + imageData[ii + 2];
              }
            }
          }
  
          result.push(total);
        }
      }
  
      translateBlocksToBits(result, blocksizeX * blocksizeY);
      return bitsToHexhash(result);
    }
  
    function bmvbhash(data, bits)
    {
      let result = [];
  
      let i; let j; let x; let y;
      let blockWidth; let blockHeight;
      let weightTop; let weightBottom; let weightLeft; let weightRight;
      let blockTop; let blockBottom; let blockLeft; let blockRight;
      let yMod; let yFrac; let yInt;
      let xMod; let xFrac; let xInt;
      let blocks = [];
      let {width, height, data: imageData} = data;
  
      let evenX = width % bits === 0;
      let evenY = height % bits === 0;
  
      if (evenX && evenY)
      {
        return bmvbhashEven(data, bits);
      }
  
      // initialize blocks array with 0s
      for (i = 0; i < bits; i++)
      {
        let block = [];
        blocks.push(block);
        for (j = 0; j < bits; j++)
        {
          block.push(0);
        }
      }
  
      blockWidth = width / bits;
      blockHeight = height / bits;
  
      for (y = 0; y < height; y++)
      {
        if (evenY)
        {
          // don't bother dividing y, if the size evenly divides by bits
          blockTop = blockBottom = Math.floor(y / blockHeight);
          weightTop = 1;
          weightBottom = 0;
        }
        else
        {
          yMod = (y + 1) % blockHeight;
          yFrac = yMod - Math.floor(yMod);
          yInt = yMod - yFrac;
  
          weightTop = (1 - yFrac);
          weightBottom = (yFrac);
  
          // yInt will be 0 on bottom/right borders and on block boundaries
          if (yInt > 0 || (y + 1) === height)
          {
            blockTop = blockBottom = Math.floor(y / blockHeight);
          }
          else
          {
            blockTop = Math.floor(y / blockHeight);
            blockBottom = Math.ceil(y / blockHeight);
          }
        }
  
        for (x = 0; x < width; x++)
        {
          let ii = (y * width + x) * 4;
  
          let avgvalue = 765;
          let alpha = imageData[ii + 3];
          if (alpha !== 0)
          {
            avgvalue = imageData[ii] + imageData[ii + 1] + imageData[ii + 2];
          }
  
          if (evenX)
          {
            blockLeft = blockRight = Math.floor(x / blockWidth);
            weightLeft = 1;
            weightRight = 0;
          }
          else
          {
            xMod = (x + 1) % blockWidth;
            xFrac = xMod - Math.floor(xMod);
            xInt = xMod - xFrac;
  
            weightLeft = (1 - xFrac);
            weightRight = xFrac;
  
            // xInt will be 0 on bottom/right borders and on block boundaries
            if (xInt > 0 || (x + 1) === width)
            {
              blockLeft = blockRight = Math.floor(x / blockWidth);
            }
            else
            {
              blockLeft = Math.floor(x / blockWidth);
              blockRight = Math.ceil(x / blockWidth);
            }
          }
  
          // add weighted pixel value to relevant blocks
          blocks[blockTop][blockLeft] += avgvalue * weightTop * weightLeft;
          blocks[blockTop][blockRight] += avgvalue * weightTop * weightRight;
          blocks[blockBottom][blockLeft] += avgvalue * weightBottom * weightLeft;
          blocks[blockBottom][blockRight] +=
            avgvalue * weightBottom * weightRight;
        }
      }
  
      for (i = 0; i < bits; i++)
      {
        let block = blocks[i];
        for (j = 0; j < bits; j++)
        {
          result.push(block[j]);
        }
      }
  
      translateBlocksToBits(result, blockWidth * blockHeight);
      return bitsToHexhash(result);
    }
  
    return bmvbhash(imageData, blockBits);
  }

Test runner

Ready to run.

Testing in
TestOps/sec
Before optimization
let result = hashImageBeforeOptimization(imageData, 8);
ready
Issue 55
let result = hashImageIssue55(imageData, 8);
ready
Issue 62
let result = hashImageIssue62(imageData, 8);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by shoniko on