From 2ec1de170091178263e53ecfce744c7a0710a409 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Fri, 5 Aug 2022 20:11:45 -0400 Subject: wip tooltip rendering --- test.js | 365 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 test.js (limited to 'test.js') diff --git a/test.js b/test.js new file mode 100644 index 0000000..d0840ec --- /dev/null +++ b/test.js @@ -0,0 +1,365 @@ +/* eslint-disable */ +// @ts-check + +import { createCanvas, registerFont } from 'canvas'; +import fs from 'fs/promises'; +import path, { dirname, join } from 'path'; +import tinycolor from 'tinycolor2'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +registerFont(join(__dirname, 'assets', 'Faithful.ttf'), { family: 'ComplianceSans' }); +registerFont(join(dirname(fileURLToPath(import.meta.url)), 'assets', 'Roboto-Regular.ttf'), { family: 'Roboto' }); + +/** @typedef {string} McItemId */ +/** @typedef {string} SbItemId */ +/** @typedef {string} MojangJson */ +/** @typedef {`${SbItemId}:${number}` | ''} SbRecipeItem */ +/** @typedef {{[Location in `${'A' | 'B' | 'C'}${1 | 2 | 3}`]: SbRecipeItem;}} SbRecipe */ +/** @typedef {'WIKI_URL' | ''} InfoType */ +/** @typedef {`${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`} Slayer */ +/** @typedef {'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'a'|'b'|'c'|'d'|'e'|'f'} code */ +/** + * @typedef RawNeuItem + * @property {McItemId} itemid + * @property {string} displayname + * @property {MojangJson} nbttag + * @property {number} damage + * @property {string[]} lore + * @property {SbRecipe} [recipe] + * @property {SbItemId} internalname + * @property {InfoType} infoType + * @property {string[]} [info] + * @property {string} crafttext + * @property {boolean} [vanilla] + * @property {boolean} [useneucraft] + * @property {Slayer} [slayer_req] + * @property {string} [clickcommand] + * @property {number} [x] + * @property {number} [y] + * @property {number} [z] + * @property {string} [island] + * @property {{ type: string; cost: any[]; result: SbItemId }[]} [recipes] + * @property {SbItemId} [parent] + * @property {boolean} [noseal] + */ + +const FormattingCodes = { + Black: '§0', + DarkBlue: '§1', + DarkGreen: '§2', + DarkAqua: '§3', + DarkRed: '§4', + DarkPurple: '§5', + Gold: '§6', + Gray: '§7', + DarkGray: '§8', + Blue: '§9', + Green: '§a', + Aqua: '§b', + Red: '§c', + LightPurple: '§d', + Yellow: '§e', + White: '§f', + + Obfuscated: '§k', + Bold: '§l', + Strikethrough: '§m', + Underline: '§n', + Italic: '§o', + Reset: '§r' +}; + +const formattingInfo = { + [FormattingCodes.Black]: { + foreground: 'rgb(0, 0, 0)', + foregroundDarker: 'rgb(0, 0, 0)', + background: 'rgb(0, 0, 0)', + backgroundDarker: 'rgb(0, 0, 0)', + ansi: '\u001b[0;30m' + }, + [FormattingCodes.DarkBlue]: { + foreground: 'rgb(0, 0, 170)', + foregroundDarker: 'rgb(0, 0, 118)', + background: 'rgb(0, 0, 42)', + backgroundDarker: 'rgb(0, 0, 29)', + ansi: '\u001b[0;34m' + }, + [FormattingCodes.DarkGreen]: { + foreground: 'rgb(0, 170, 0)', + foregroundDarker: 'rgb(0, 118, 0)', + background: 'rgb(0, 42, 0)', + backgroundDarker: 'rgb(0, 29, 0)', + ansi: '\u001b[0;32m' + }, + [FormattingCodes.DarkAqua]: { + foreground: 'rgb(0, 170, 170)', + foregroundDarker: 'rgb(0, 118, 118)', + background: 'rgb(0, 42, 42)', + backgroundDarker: 'rgb(0, 29, 29)', + ansi: '\u001b[0;36m' + }, + [FormattingCodes.DarkRed]: { + foreground: 'rgb(170, 0, 0)', + foregroundDarker: 'rgb(118, 0, 0)', + background: 'rgb(42, 0, 0)', + backgroundDarker: 'rgb(29, 0, 0)', + ansi: '\u001b[0;31m' + }, + [FormattingCodes.DarkPurple]: { + foreground: 'rgb(170, 0, 170)', + foregroundDarker: 'rgb(118, 0, 118)', + background: 'rgb(42, 0, 42)', + backgroundDarker: 'rgb(29, 0, 29)', + ansi: '\u001b[0;35m' + }, + [FormattingCodes.Gold]: { + foreground: 'rgb(255, 170, 0)', + foregroundDarker: 'rgb(178, 118, 0)', + background: 'rgb(42, 42, 0)', + backgroundDarker: 'rgb(29, 29, 0)', + ansi: '\u001b[0;33m' + }, + [FormattingCodes.Gray]: { + foreground: 'rgb(170, 170, 170)', + foregroundDarker: 'rgb(118, 118, 118)', + background: 'rgb(42, 42, 42)', + backgroundDarker: 'rgb(29, 29, 29)', + ansi: '\u001b[0;37m' + }, + [FormattingCodes.DarkGray]: { + foreground: 'rgb(85, 85, 85)', + foregroundDarker: 'rgb(59, 59, 59)', + background: 'rgb(21, 21, 21)', + backgroundDarker: 'rgb(14, 14, 14)', + ansi: '\u001b[0;90m' + }, + [FormattingCodes.Blue]: { + foreground: 'rgb(85, 85, 255)', + foregroundDarker: 'rgb(59, 59, 178)', + background: 'rgb(21, 21, 63)', + backgroundDarker: 'rgb(14, 14, 44)', + ansi: '\u001b[0;94m' + }, + [FormattingCodes.Green]: { + foreground: 'rgb(85, 255, 85)', + foregroundDarker: 'rgb(59, 178, 59)', + background: 'rgb(21, 63, 21)', + backgroundDarker: 'rgb(14, 44, 14)', + ansi: '\u001b[0;92m' + }, + [FormattingCodes.Aqua]: { + foreground: 'rgb(85, 255, 255)', + foregroundDarker: 'rgb(59, 178, 178)', + background: 'rgb(21, 63, 63)', + backgroundDarker: 'rgb(14, 44, 44)', + ansi: '\u001b[0;96m' + }, + [FormattingCodes.Red]: { + foreground: 'rgb(255, 85, 85)', + foregroundDarker: 'rgb(178, 59, 59)', + background: 'rgb(63, 21, 21)', + backgroundDarker: 'rgb(44, 14, 14)', + ansi: '\u001b[0;91m' + }, + [FormattingCodes.LightPurple]: { + foreground: 'rgb(255, 85, 255)', + foregroundDarker: 'rgb(178, 59, 178)', + background: 'rgb(63, 21, 63)', + backgroundDarker: 'rgb(44, 14, 44)', + ansi: '\u001b[0;95m' + }, + [FormattingCodes.Yellow]: { + foreground: 'rgb(255, 255, 85)', + foregroundDarker: 'rgb(178, 178, 59)', + background: 'rgb(63, 63, 21)', + backgroundDarker: 'rgb(44, 44, 14)', + ansi: '\u001b[0;93m' + }, + [FormattingCodes.White]: { + foreground: 'rgb(255, 255, 255)', + foregroundDarker: 'rgb(178, 178, 178)', + background: 'rgb(63, 63, 63)', + backgroundDarker: 'rgb(44, 44, 44)', + ansi: '\u001b[0;97m' + }, + + [FormattingCodes.Obfuscated]: { ansi: '\u001b[8m' }, + [FormattingCodes.Bold]: { ansi: '\u001b[1m' }, + [FormattingCodes.Strikethrough]: { ansi: '\u001b[9m' }, + [FormattingCodes.Underline]: { ansi: '\u001b[4m' }, + [FormattingCodes.Italic]: { ansi: '\u001b[3m' }, + [FormattingCodes.Reset]: { ansi: '\u001b[0m' } +}; + +/** + * stolen from NEU + * @param {string} displayname + * @returns {code} + */ +function getPrimaryColourCode(displayname) { + let lastColourCode = -99; + let currentColour = 0; + const mostCommon = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (let i = 0; i < displayname.length; i++) { + const c = displayname.charAt(i); + if (c === '\u00A7') { + lastColourCode = i; + } else if (lastColourCode === i - 1) { + const colIndex = '0123456789abcdef'.indexOf(c); + if (colIndex >= 0) { + currentColour = colIndex; + } else { + currentColour = 0; + } + } else if ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(c) >= 0) { + if (currentColour > 0) { + mostCommon[currentColour] = mostCommon[currentColour]++; + } + } + } + let mostCommonCount = 0; + for (let index = 0; index < mostCommon.length; index++) { + if (mostCommon[index] > mostCommonCount) { + mostCommonCount = mostCommon[index]; + currentColour = index; + } + } + + /** @type {code} */ + // @ts-ignore + const code = '0123456789abcdef'.charAt(currentColour); + return code; +} + +/** + * @param {number} decimal + */ +function decimalToHex(decimal) { + return decimal.toString(16).padStart(6, '0'); +} + +/** + * @param {RawNeuItem} item + * @returns {Buffer} + */ +function tooltip(item) { + const background = '#100010'; + + const width = 1920; + const height = 1080; + const scale = 10; + + const itemRender = createCanvas(width, height), + ctx = itemRender.getContext('2d'); + + // ctx.fillStyle = '#000'; + // ctx.fillRect(0, 0, width, height); + + // ctx.globalAlpha = 0.94; + ctx.fillStyle = background; + + // top outside + ctx.fillRect(scale, 0, width - 2 * scale, scale); + + // bottom outside + ctx.fillRect(scale, height - scale, width - 2 * scale, scale); + + // left outside + ctx.fillRect(0, scale, scale, height - 2 * scale); + + // right outside + ctx.fillRect(width - scale, scale, scale, height - 2 * scale); + + // middle + ctx.fillRect(2 * scale, 2 * scale, width - 4 * scale, height - 4 * scale); + + // ctx.globalAlpha = 0.78; + + const borderColorStart = parseInt(new tinycolor(getPrimaryColour(item.displayname)).toHex(), 16); + const borderColorEnd = ((borderColorStart & 0xfefefe) >> 1) | (borderColorStart & 0xff000000); + + const borderColorStartStr = `#${decimalToHex(borderColorStart)}`; + const borderColorEndStr = `#${decimalToHex(borderColorEnd)}`; + + console.log(borderColorStartStr, borderColorEndStr); + + ctx.fillStyle = borderColorStartStr; + + // top highlight + ctx.fillRect(scale, scale, width - 2 * scale, scale); + + // left highlight + ctx.fillRect(scale, 2 * scale, scale, height - 3 * scale); + + // bottom highlight + { + const x = 2 * scale, + y = height - 2 * scale, + w = width - 3 * scale, + h = scale; + const gradient = ctx.createLinearGradient(x, y, x + w, y + h); + gradient.addColorStop(0, borderColorStartStr); + gradient.addColorStop(1, borderColorEndStr); + ctx.fillStyle = gradient; + + ctx.fillRect(x, y, w, h); + } + + // right highlight + { + const x = width - 2 * scale, + y = 2 * scale, + w = scale, + h = height - 4 * scale; + const gradient = ctx.createLinearGradient(x, y, x + w, y + h); + gradient.addColorStop(0, borderColorStartStr); + gradient.addColorStop(1, borderColorEndStr); + ctx.fillStyle = gradient; + + ctx.fillRect(x, y, w, h); + } + + ctx.font = `50px ComplianceSans`; + ctx.fillText(stripCodes(item.displayname), scale * 4, scale * 7); + + for (let i = 0; i < item.lore.length; i++) { + const line = item.lore[i]; + + ctx.fillStyle = `#${decimalToHex(parseInt(new tinycolor(getPrimaryColour(line)).toHex(), 16))}`; + ctx.fillText(stripCodes(line), scale * 4, scale * (7 + (i + 1) * 5)); + } + + return itemRender.toBuffer('image/png'); +} + +/** + * @param {string} displayname + */ +function getPrimaryColour(displayname) { + const code = getPrimaryColourCode(displayname); + return formattingInfo[`§${code}`].foregroundDarker; +} + +/** + * @param {string} str + * @returns {string} + */ +function stripCodes(str) { + for (const format in formattingInfo) { + // @ts-ignore + str = str.replaceAll(new RegExp(format, 'ig'), ''); + } + return str; +} + +const repo = path.join(__dirname, 'neu-item-repo-dangerous'); +const itemPath = path.join(repo, 'items'); +const items = await fs.readdir(itemPath); + +const randomItem = items[Math.floor(Math.random() * items.length)]; +/** @type {RawNeuItem} */ +const item = (await import(path.join(itemPath, randomItem), { assert: { type: 'json' } })).default; + +console.log(randomItem); +fs.writeFile('./test.png', tooltip(item)); -- cgit