text: 
  const median = (data) => {
    const mdarr = data.slice(0).sort((a, b) => a - b);
    if (mdarr.length % 2 === 0) {
      return (mdarr[mdarr.length / 2] + mdarr[mdarr.length / 2 + 1]) / 2.0;
    }
    return mdarr[Math.floor(mdarr.length / 2)];
  };
  
  const translateBlocksToBits = (blocks, pixelsPerBlock) => {
    const newblocks = blocks;
    const halfBlockValue = (pixelsPerBlock * 256 * 3) / 2;
    const bandsize = blocks.length / 4;
  
    // Compare medians across four hor  zontal bands
    for (let i = 0; i < 4; i += 1) {
      const m = median(blocks.slice(i * bandsize, (i + 1) * bandsize));
      for (let j = i * bandsize; j < (i + 1) * bandsize; j += 1) {
        const 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
        newblocks[j] = Number(
          v > m || (Math.abs(v - m) < 1 && m > halfBlockValue),
        );
      }
    }
  };
  
  const bitsToHexhash = (bitsArray) => {
    const hex = [];
    for (let i = 0; i < bitsArray.length; i += 4) {
      const nibble = bitsArray.slice(i, i + 4);
      hex.push(parseInt(nibble.join(''), 2).toString(16));
    }
  
    return hex.join('');
  };
  
  const bmvbhashEven = (data, bits) => {
    const blocksizeX = Math.floor(data.width / bits);
    const blocksizeY = Math.floor(data.height / bits);
  
    const result = [];
  
    for (let y = 0; y < bits; y += 1) {
      for (let x = 0; x < bits; x += 1) {
        let total = 0;
  
        for (let iy = 0; iy < blocksizeY; iy += 1) {
          for (let ix = 0; ix < blocksizeX; ix += 1) {
            const cx = x * blocksizeX + ix;
            const cy = y * blocksizeY + iy;
            const ii = (cy * data.width + cx) * 4;
  
            const 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);
  };
  
  const bmvbhash = (data, bits) => {
    const result = [];
    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;
    const blocks = [];
  
    const evenX = data.width % bits === 0;
    const evenY = data.height % bits === 0;
  
    if (evenX && evenY) {
      return bmvbhashEven(data, bits);
    }
  
    // initialize blocks array with 0s
    for (let i = 0; i < bits; i += 1) {
      blocks.push([]);
      for (let j = 0; j < bits; j += 1) {
        blocks[i].push(0);
      }
    }
  
    const blockWidth = data.width / bits;
    const blockHeight = data.height / bits;
  
    for (let y = 0; y < data.height; y += 1) {
      if (evenY) {
        // don't bother dividing y, if the size evenly divides by bits
        blockBottom = Math.floor(y / blockHeight);
        blockTop = blockBottom;
        weightTop = 1;
        weightBottom = 0;
      } else {
        yMod = (y + 1) % blockHeight;
        yFrac = yMod - Math.floor(yMod);
        yInt = yMod - yFrac;
  
        weightTop = 1 - yFrac;
        weightBottom = yFrac;
  
        // y_int will be 0 on bottom/right borders and on block boundaries
        if (yInt > 0 || y + 1 === data.height) {
          blockBottom = Math.floor(y / blockHeight);
          blockTop = blockBottom;
        } else {
          blockTop = Math.floor(y / blockHeight);
          blockBottom = Math.ceil(y / blockHeight);
        }
      }
  
      for (let x = 0; x < data.width; x += 1) {
        let avgvalue;
        const ii = (y * data.width + x) * 4;
        const 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) {
          blockRight = Math.floor(x / blockWidth);
          blockLeft = blockRight;
          weightLeft = 1;
          weightRight = 0;
        } else {
          xMod = (x + 1) % blockWidth;
          xFrac = xMod - Math.floor(xMod);
          xInt = xMod - xFrac;
  
          weightLeft = 1 - xFrac;
          weightRight = xFrac;
  
          // x_int will be 0 on bottom/right borders and on block boundaries
          if (xInt > 0 || x + 1 === data.width) {
            blockRight = Math.floor(x / blockWidth);
            blockLeft = blockRight;
          } 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 (let i = 0; i < bits; i += 1) {
      for (let j = 0; j < bits; j += 1) {
        result.push(blocks[i][j]);
      }
    }
  
    translateBlocksToBits(result, blockWidth * blockHeight);
    return bitsToHexhash(result);
  };
  
  module.exports = (imgData, bits, method) => {
    let hash;
  
    if (method === 1) {
      hash = bmvbhashEven(imgData, bits);
    } else if (method === 2) {
      hash = bmvbhash(imgData, bits);
    } else {
      throw new Error('Bad hashing method');
    }
  
    return hash;
  };
  