🏡 index : quine-global/quintodrome.git

import { describe, it, expect } from 'vitest'
import { decode, naturalSize } from './thumbhash'

// Digests pinned from evanw/thumbhash's reference decoder (vendored at
// core/artwork/thumbhash/testdata/thumbhash.js), so any drift from it fails here.
const fnv = (bytes) => {
  let h = 2166136261 >>> 0
  for (const b of bytes) {
    h ^= b
    h = Math.imul(h, 16777619) >>> 0
  }
  return h.toString(16)
}

const REFERENCE = [
  ['alpha', 'JOiFBQ4nkIexh3p4iA8uB+lYhIeAh3d4dw==', 32, 32, '500141bc'],
  ['landscape', '3wcOFJpwh4eBh3d4iIePgAj3hw==', 32, 18, '18465aed'],
  ['portrait', '3/cNFBqBB4iId4d3d4iAjwj4hw==', 18, 32, '56500cc1'],
  ['solid', 'HoUBBwB4eHeHd3hweId3h3h4B2+Ih4gA', 32, 32, '39cc5c5'],
  ['square', 'H/gNBxpwh4dwd3eIiHd3iHeHeJ+dcH8I', 32, 32, '740d8afc'],
  ['tiny', 'HoU9tx4I9wiIh4hwj3CI+AiIcH/494cP', 32, 32, 'f2555987'],
]

describe('thumbhash decode', () => {
  it.each(REFERENCE)(
    'reproduces the reference pixels for %s',
    (_name, hash, w, h, digest) => {
      expect(fnv(decode(hash, w, h))).toEqual(digest)
    },
  )

  it.each(REFERENCE)(
    'reports the reference natural size for %s',
    (_name, hash, w, h) => {
      expect(naturalSize(hash)).toEqual({ width: w, height: h })
    },
  )

  it('decodes to any requested grid, not just the natural one', () => {
    const [, hash] = REFERENCE[4]
    const pixels = decode(hash, 8, 5)
    expect(pixels).toHaveLength(8 * 5 * 4)
    // Opaque hash: every alpha byte is saturated.
    for (let i = 3; i < pixels.length; i += 4) {
      expect(pixels[i]).toBe(255)
    }
  })

  it('carries alpha through for a hash that has it', () => {
    const [, hash, w, h] = REFERENCE[0]
    const pixels = decode(hash, w, h)
    const alphas = new Set()
    for (let i = 3; i < pixels.length; i += 4) {
      alphas.add(pixels[i])
    }
    expect(alphas.size).toBeGreaterThan(1)
  })

  it.each([
    ['empty', ''],
    ['too short to hold a header', 'AAAA'],
    ['not base64', '!!!not-a-thumbhash!!!'],
  ])('throws on a hash that is %s', (_name, hash) => {
    expect(() => decode(hash, 32, 32)).toThrow()
  })
})