UNPKG

@jeremyckahn/farmhand

Version:
108 lines (84 loc) 3.34 kB
import { Buffer } from 'buffer' import configureJimp from '@jimp/custom' import jimpPng from '@jimp/png' import { animals, pixel } from '../img/index.js' import { cowColors } from '../enums.js' import { COW_COLORS_HEX_MAP } from '../constants.js' import { memoize } from './memoize.js' import { convertStringToInteger } from './convertStringToInteger.js' const Jimp = configureJimp({ types: [jimpPng], }) const colorizeCowTemplate = (() => { // TODO: Remove unused cowImageWidth, cowImageHeight, and // cowImageFactoryCanvas variables. // Instantiating a canvas directly in the module scope also poses runtime // risks in non-browser/SSR environments. const cowImageWidth = 48 const cowImageHeight = 48 const cowImageFactoryCanvas = document.createElement('canvas') cowImageFactoryCanvas.setAttribute('height', String(cowImageHeight)) cowImageFactoryCanvas.setAttribute('width', String(cowImageWidth)) const cachedCowImages: Record<string, string> = {} // https://stackoverflow.com/a/5624139 const hexToRgb = memoize((hex: string) => { const [, r, g, b] = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec( hex ) ?? ['', '0', '0', '0'] return { r: parseInt(r, 16), g: parseInt(g, 16), b: parseInt(b, 16), } }) /** * @param cowTemplate Base64 representation of an image * @param color * @returns Base64 representation of an image */ return async (cowTemplate: string, color: farmhand.cowColors) => { if (color === cowColors.RAINBOW) return animals.cow.rainbow const imageKey = `${color}_${cowTemplate}` if (cachedCowImages[imageKey]) return cachedCowImages[imageKey] try { // `data:image/png;base64,` needs to be removed from the base64 string // before being provided to Buffer. // https://github.com/oliver-moran/jimp/issues/231#issuecomment-282167737 const cowTemplateBuffer = Buffer.from( cowTemplate.split(',')[1] ?? '', 'base64' ) const image = await Jimp.read(cowTemplateBuffer) image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y) { const { r, g, b } = Jimp.intToRGBA(image.getPixelColor(x, y)) // rgb(102, 102, 102) represents the color to replace in the template // source images (#666). if (r === 102 && g === 102 && b === 102) { const cowColorRgb = hexToRgb(COW_COLORS_HEX_MAP[color]) const colorNumber = Jimp.rgbaToInt( cowColorRgb.r, cowColorRgb.g, cowColorRgb.b, 255 ) image.setPixelColor(colorNumber, x, y) } }) cachedCowImages[imageKey] = await image.getBase64Async(Jimp.MIME_PNG) return cachedCowImages[imageKey] } catch (e) { // Jimp.read() expectedly errors out when it receives an empty buffer, // which it will in some unit tests. if (import.meta.env?.MODE !== 'test') { console.error(e) } return pixel } } })() export const getCowImage = async (cow: farmhand.cow): Promise<string> => { const cowIdNumber = convertStringToInteger(cow.id) const { variations } = animals.cow const cowTemplate = variations[cowIdNumber % variations.length] return await colorizeCowTemplate(cowTemplate, cow.color as farmhand.cowColors) }