diff --git a/dist/extension.zip b/dist/extension.zip index 3c0418d..1a42835 100644 Binary files a/dist/extension.zip and b/dist/extension.zip differ diff --git a/dist/extension/birb.js b/dist/extension/birb.js index 08ef0b2..a14bea5 100644 --- a/dist/extension/birb.js +++ b/dist/extension/birb.js @@ -471,6 +471,62 @@ } } + /** + * Load a sprite sheet image and convert it to a 2D array of palette color names + * @param {string} src - URL or data URI of the sprite sheet image + * @param {boolean} [templateColors] - Whether to map pixel colors to palette names + * @returns {Promise} + */ + function loadSpriteSheetPixels(src, templateColors = true) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const pixels = imageData.data; + const hexArray = []; + for (let y = 0; y < img.height; y++) { + const row = []; + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const r = pixels[index]; + const g = pixels[index + 1]; + const b = pixels[index + 2]; + const a = pixels[index + 3]; + if (a === 0) { + row.push(PALETTE.TRANSPARENT); + continue; + } + const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; + if (!templateColors) { + row.push(hex); + continue; + } + if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { + row.push(hex); + continue; + } + row.push(SPRITE_SHEET_COLOR_MAP[hex]); + } + hexArray.push(row); + } + resolve(hexArray); + }; + img.onerror = (err) => { + reject(err); + }; + }); + } + /** @type {Record} */ const SPECIES = Object.fromEntries( Object.entries(species).map(([id, data]) => [ @@ -2100,7 +2156,7 @@ outline: none !important; box-shadow: none !important; }`; - const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC"; + const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII="; const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII="; const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg=="; @@ -2236,7 +2292,7 @@ }), new Separator(), new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }), - new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false), + new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false), ]; /** @type {Birb} */ @@ -3205,63 +3261,6 @@ draw(); } - /** - * Load the sprite sheet and return the pixel-map template - * @param {string} dataUri - * @param {boolean} [templateColors] - * @returns {Promise} - */ - function loadSpriteSheetPixels(dataUri, templateColors = true) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = dataUri; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const pixels = imageData.data; - const hexArray = []; - for (let y = 0; y < img.height; y++) { - const row = []; - for (let x = 0; x < img.width; x++) { - const index = (y * img.width + x) * 4; - const r = pixels[index]; - const g = pixels[index + 1]; - const b = pixels[index + 2]; - const a = pixels[index + 3]; - if (a === 0) { - row.push(PALETTE.TRANSPARENT); - continue; - } - const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; - if (!templateColors) { - row.push(hex); - continue; - } - if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { - // Return the color as-is if not found in the map - row.push(hex); - continue; - } - row.push(SPRITE_SHEET_COLOR_MAP[hex]); - } - hexArray.push(row); - } - resolve(hexArray); - }; - img.onerror = (err) => { - reject(err); - }; - }); - } - initializeApplication(new BrowserExtensionContext()); })(); diff --git a/dist/extension/manifest.json b/dist/extension/manifest.json index c857fb9..c1c94d8 100644 --- a/dist/extension/manifest.json +++ b/dist/extension/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Pocket Bird", "description": "It's a pet bird in your browser, what more could you want?", - "version": "2026.3.8", + "version": "2026.3.11", "homepage_url": "https://idreesinc.com", "icons": { "48": "images/icons/transparent/48x48x1.png", diff --git a/dist/obsidian/main.js b/dist/obsidian/main.js index 799da3f..42fd14c 100644 --- a/dist/obsidian/main.js +++ b/dist/obsidian/main.js @@ -1,7 +1,7 @@ const { Plugin, Notice } = require('obsidian'); module.exports = class PocketBird extends Plugin { onload() { - console.log("Loading Pocket Bird version 2026.3.8..."); + console.log("Loading Pocket Bird version 2026.3.11..."); const OBSIDIAN_PLUGIN = this; (function () { 'use strict'; @@ -476,6 +476,62 @@ module.exports = class PocketBird extends Plugin { } } + /** + * Load a sprite sheet image and convert it to a 2D array of palette color names + * @param {string} src - URL or data URI of the sprite sheet image + * @param {boolean} [templateColors] - Whether to map pixel colors to palette names + * @returns {Promise} + */ + function loadSpriteSheetPixels(src, templateColors = true) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const pixels = imageData.data; + const hexArray = []; + for (let y = 0; y < img.height; y++) { + const row = []; + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const r = pixels[index]; + const g = pixels[index + 1]; + const b = pixels[index + 2]; + const a = pixels[index + 3]; + if (a === 0) { + row.push(PALETTE.TRANSPARENT); + continue; + } + const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; + if (!templateColors) { + row.push(hex); + continue; + } + if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { + row.push(hex); + continue; + } + row.push(SPRITE_SHEET_COLOR_MAP[hex]); + } + hexArray.push(row); + } + resolve(hexArray); + }; + img.onerror = (err) => { + reject(err); + }; + }); + } + /** @type {Record} */ const SPECIES = Object.fromEntries( Object.entries(species).map(([id, data]) => [ @@ -2133,7 +2189,7 @@ module.exports = class PocketBird extends Plugin { outline: none !important; box-shadow: none !important; }`; - const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC"; + const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII="; const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII="; const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg=="; @@ -2269,7 +2325,7 @@ module.exports = class PocketBird extends Plugin { }), new Separator(), new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }), - new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false), + new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false), ]; /** @type {Birb} */ @@ -3238,63 +3294,6 @@ module.exports = class PocketBird extends Plugin { draw(); } - /** - * Load the sprite sheet and return the pixel-map template - * @param {string} dataUri - * @param {boolean} [templateColors] - * @returns {Promise} - */ - function loadSpriteSheetPixels(dataUri, templateColors = true) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = dataUri; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const pixels = imageData.data; - const hexArray = []; - for (let y = 0; y < img.height; y++) { - const row = []; - for (let x = 0; x < img.width; x++) { - const index = (y * img.width + x) * 4; - const r = pixels[index]; - const g = pixels[index + 1]; - const b = pixels[index + 2]; - const a = pixels[index + 3]; - if (a === 0) { - row.push(PALETTE.TRANSPARENT); - continue; - } - const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; - if (!templateColors) { - row.push(hex); - continue; - } - if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { - // Return the color as-is if not found in the map - row.push(hex); - continue; - } - row.push(SPRITE_SHEET_COLOR_MAP[hex]); - } - hexArray.push(row); - } - resolve(hexArray); - }; - img.onerror = (err) => { - reject(err); - }; - }); - } - initializeApplication(new ObsidianContext()); })(); diff --git a/dist/obsidian/manifest.json b/dist/obsidian/manifest.json index c8fca71..40deb07 100644 --- a/dist/obsidian/manifest.json +++ b/dist/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "pocket-bird", "name": "Pocket Bird", - "version": "2026.3.8", + "version": "2026.3.11", "minAppVersion": "0.15.0", "description": "Add a pet bird to fly around your notes and keep you company!", "author": "Idrees Hassan", diff --git a/dist/userscript/birb.user.js b/dist/userscript/birb.user.js index 65e8e1c..c3db689 100644 --- a/dist/userscript/birb.user.js +++ b/dist/userscript/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2026.3.8 +// @version 2026.3.11 // @description It's a pet bird in your browser, what more could you want? // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js @@ -485,6 +485,62 @@ } } + /** + * Load a sprite sheet image and convert it to a 2D array of palette color names + * @param {string} src - URL or data URI of the sprite sheet image + * @param {boolean} [templateColors] - Whether to map pixel colors to palette names + * @returns {Promise} + */ + function loadSpriteSheetPixels(src, templateColors = true) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const pixels = imageData.data; + const hexArray = []; + for (let y = 0; y < img.height; y++) { + const row = []; + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const r = pixels[index]; + const g = pixels[index + 1]; + const b = pixels[index + 2]; + const a = pixels[index + 3]; + if (a === 0) { + row.push(PALETTE.TRANSPARENT); + continue; + } + const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; + if (!templateColors) { + row.push(hex); + continue; + } + if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { + row.push(hex); + continue; + } + row.push(SPRITE_SHEET_COLOR_MAP[hex]); + } + hexArray.push(row); + } + resolve(hexArray); + }; + img.onerror = (err) => { + reject(err); + }; + }); + } + /** @type {Record} */ const SPECIES = Object.fromEntries( Object.entries(species).map(([id, data]) => [ @@ -2095,7 +2151,7 @@ outline: none !important; box-shadow: none !important; }`; - const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC"; + const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII="; const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII="; const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg=="; @@ -2231,7 +2287,7 @@ }), new Separator(), new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }), - new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false), + new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false), ]; /** @type {Birb} */ @@ -3200,63 +3256,6 @@ draw(); } - /** - * Load the sprite sheet and return the pixel-map template - * @param {string} dataUri - * @param {boolean} [templateColors] - * @returns {Promise} - */ - function loadSpriteSheetPixels(dataUri, templateColors = true) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = dataUri; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const pixels = imageData.data; - const hexArray = []; - for (let y = 0; y < img.height; y++) { - const row = []; - for (let x = 0; x < img.width; x++) { - const index = (y * img.width + x) * 4; - const r = pixels[index]; - const g = pixels[index + 1]; - const b = pixels[index + 2]; - const a = pixels[index + 3]; - if (a === 0) { - row.push(PALETTE.TRANSPARENT); - continue; - } - const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; - if (!templateColors) { - row.push(hex); - continue; - } - if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { - // Return the color as-is if not found in the map - row.push(hex); - continue; - } - row.push(SPRITE_SHEET_COLOR_MAP[hex]); - } - hexArray.push(row); - } - resolve(hexArray); - }; - img.onerror = (err) => { - reject(err); - }; - }); - } - initializeApplication(new UserScriptContext()); })(); diff --git a/dist/web/birb.embed.js b/dist/web/birb.embed.js index b994cba..ed74e5e 100644 --- a/dist/web/birb.embed.js +++ b/dist/web/birb.embed.js @@ -471,6 +471,62 @@ } } + /** + * Load a sprite sheet image and convert it to a 2D array of palette color names + * @param {string} src - URL or data URI of the sprite sheet image + * @param {boolean} [templateColors] - Whether to map pixel colors to palette names + * @returns {Promise} + */ + function loadSpriteSheetPixels(src, templateColors = true) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const pixels = imageData.data; + const hexArray = []; + for (let y = 0; y < img.height; y++) { + const row = []; + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const r = pixels[index]; + const g = pixels[index + 1]; + const b = pixels[index + 2]; + const a = pixels[index + 3]; + if (a === 0) { + row.push(PALETTE.TRANSPARENT); + continue; + } + const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; + if (!templateColors) { + row.push(hex); + continue; + } + if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { + row.push(hex); + continue; + } + row.push(SPRITE_SHEET_COLOR_MAP[hex]); + } + hexArray.push(row); + } + resolve(hexArray); + }; + img.onerror = (err) => { + reject(err); + }; + }); + } + /** @type {Record} */ const SPECIES = Object.fromEntries( Object.entries(species).map(([id, data]) => [ @@ -2075,7 +2131,7 @@ outline: none !important; box-shadow: none !important; }`; - const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC"; + const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII="; const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII="; const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg=="; @@ -2211,7 +2267,7 @@ }), new Separator(), new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }), - new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false), + new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false), ]; /** @type {Birb} */ @@ -3180,63 +3236,6 @@ draw(); } - /** - * Load the sprite sheet and return the pixel-map template - * @param {string} dataUri - * @param {boolean} [templateColors] - * @returns {Promise} - */ - function loadSpriteSheetPixels(dataUri, templateColors = true) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = dataUri; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const pixels = imageData.data; - const hexArray = []; - for (let y = 0; y < img.height; y++) { - const row = []; - for (let x = 0; x < img.width; x++) { - const index = (y * img.width + x) * 4; - const r = pixels[index]; - const g = pixels[index + 1]; - const b = pixels[index + 2]; - const a = pixels[index + 3]; - if (a === 0) { - row.push(PALETTE.TRANSPARENT); - continue; - } - const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; - if (!templateColors) { - row.push(hex); - continue; - } - if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { - // Return the color as-is if not found in the map - row.push(hex); - continue; - } - row.push(SPRITE_SHEET_COLOR_MAP[hex]); - } - hexArray.push(row); - } - resolve(hexArray); - }; - img.onerror = (err) => { - reject(err); - }; - }); - } - initializeApplication(new LocalContext()); })(); diff --git a/dist/web/birb.js b/dist/web/birb.js index b994cba..ed74e5e 100644 --- a/dist/web/birb.js +++ b/dist/web/birb.js @@ -471,6 +471,62 @@ } } + /** + * Load a sprite sheet image and convert it to a 2D array of palette color names + * @param {string} src - URL or data URI of the sprite sheet image + * @param {boolean} [templateColors] - Whether to map pixel colors to palette names + * @returns {Promise} + */ + function loadSpriteSheetPixels(src, templateColors = true) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const pixels = imageData.data; + const hexArray = []; + for (let y = 0; y < img.height; y++) { + const row = []; + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const r = pixels[index]; + const g = pixels[index + 1]; + const b = pixels[index + 2]; + const a = pixels[index + 3]; + if (a === 0) { + row.push(PALETTE.TRANSPARENT); + continue; + } + const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; + if (!templateColors) { + row.push(hex); + continue; + } + if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { + row.push(hex); + continue; + } + row.push(SPRITE_SHEET_COLOR_MAP[hex]); + } + hexArray.push(row); + } + resolve(hexArray); + }; + img.onerror = (err) => { + reject(err); + }; + }); + } + /** @type {Record} */ const SPECIES = Object.fromEntries( Object.entries(species).map(([id, data]) => [ @@ -2075,7 +2131,7 @@ outline: none !important; box-shadow: none !important; }`; - const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC"; + const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII="; const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII="; const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg=="; @@ -2211,7 +2267,7 @@ }), new Separator(), new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }), - new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false), + new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false), ]; /** @type {Birb} */ @@ -3180,63 +3236,6 @@ draw(); } - /** - * Load the sprite sheet and return the pixel-map template - * @param {string} dataUri - * @param {boolean} [templateColors] - * @returns {Promise} - */ - function loadSpriteSheetPixels(dataUri, templateColors = true) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = dataUri; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const pixels = imageData.data; - const hexArray = []; - for (let y = 0; y < img.height; y++) { - const row = []; - for (let x = 0; x < img.width; x++) { - const index = (y * img.width + x) * 4; - const r = pixels[index]; - const g = pixels[index + 1]; - const b = pixels[index + 2]; - const a = pixels[index + 3]; - if (a === 0) { - row.push(PALETTE.TRANSPARENT); - continue; - } - const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; - if (!templateColors) { - row.push(hex); - continue; - } - if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { - // Return the color as-is if not found in the map - row.push(hex); - continue; - } - row.push(SPRITE_SHEET_COLOR_MAP[hex]); - } - hexArray.push(row); - } - resolve(hexArray); - }; - img.onerror = (err) => { - reject(err); - }; - }); - } - initializeApplication(new LocalContext()); })(); diff --git a/editor/editor.js b/editor/editor.js new file mode 100644 index 0000000..4a2c870 --- /dev/null +++ b/editor/editor.js @@ -0,0 +1,321 @@ +// @ts-check +import { SPRITE_SHEET_COLOR_MAP, PALETTE, loadSpriteSheetPixels } from '../src/animation/sprites.js'; +import Layer, { TAG } from '../src/animation/layer.js'; +import Frame from '../src/animation/frame.js'; +import { Directions, getLayerPixels } from '../src/shared.js'; +import species from '../src/species.js'; + +const COLOR_MAP = SPRITE_SHEET_COLOR_MAP; +const SPRITE_PATH = "../sprites/birb.png"; +const SPRITE_SIZE = 32; +/** @type {Array<{tag: string, label: string}>} */ +const AVAILABLE_TAGS = [ + { tag: TAG.TUFT, label: "Tuft" }, +]; + +/** @type {Record} */ +const DEFAULT_OVERRIDES = { + "hood": "face", + "nose": "face" +}; +const IGNORED_PARTS = new Set( + ["transparent", "border", "heart", "heart-border", "heart-shine", "feather-spine"] +); + +/** @type {HTMLCanvasElement} */ +// @ts-ignore +const canvas = document.getElementById("preview"); +/** @type {CanvasRenderingContext2D} */ +// @ts-ignore +const ctx = canvas.getContext("2d"); +/** @type {HTMLElement} */ +// @ts-ignore +const editor = document.getElementById("editor"); +const colorPickerInput = document.createElement("input"); +/** @type {HTMLElement} */ +// @ts-ignore +const jsonElement = document.getElementById("json"); + +/** @type {string|null} */ +let selectedPart = null; +/** @type {HTMLElement|null} */ +let selectedColorElement = null; +/** @type {Record} */ +const colorElements = {}; + + +/** @type {Record} */ +let currentSpecies = { ...species.bluebird.colors }; +let colorHistory = [{ ...currentSpecies }]; +let historyIndex = 0; + + +/** @type {Set} */ +const currentTags = new Set(); + +/** @type {Frame|null} */ +let baseFrame = null; +const spriteCanvas = document.createElement('canvas'); +spriteCanvas.width = canvas.width; +spriteCanvas.height = canvas.height; +/** @type {CanvasRenderingContext2D} */ +// @ts-ignore +const spriteCtx = spriteCanvas.getContext('2d'); + +function drawBackground() { + const patternSize = 2; + const colors = ["#edf0f4", "#dadbe0"]; + for (let y = 0; y < canvas.height; y += patternSize) { + for (let x = 0; x < canvas.width; x += patternSize) { + ctx.fillStyle = ((x / patternSize + y / patternSize) % 2 === 0) ? colors[0] : colors[1]; + ctx.fillRect(x, y, patternSize, patternSize); + } + } +} + +/** + * Build the full palette color scheme from the current species settings + * @returns {Record} + */ +function buildColorScheme() { + /** @type {Record} */ + const scheme = {}; + for (const paletteName of Object.values(PALETTE)) { + scheme[paletteName] = getColor(paletteName); + } + return scheme; +} + +function draw() { + if (!baseFrame) { + return; + } + drawBackground(); + baseFrame.draw(spriteCtx, Directions.RIGHT, 1, buildColorScheme(), [...currentTags]); + ctx.drawImage(spriteCanvas, 0, 0); +} + +function updateColors() { + const lastColors = colorHistory[historyIndex]; + let changed = false; + for (const part of Object.keys(currentSpecies)) { + if (currentSpecies[part] !== lastColors[part]) { + changed = true; + break; + } + } + if (!changed) { + for (const part of Object.keys(lastColors)) { + if (!(part in currentSpecies)) { + changed = true; + break; + } + } + } + if (changed) { + colorHistory = colorHistory.slice(0, historyIndex + 1); + colorHistory.push({ ...currentSpecies }); + historyIndex++; + } + updateJson(); + draw(); +} + +function loadEditor() { + for (const [color, part] of Object.entries(COLOR_MAP)) { + if (IGNORED_PARTS.has(part)) { + continue; + } + const item = createColorItem(part, getColor(part) || color); + editor.appendChild(item); + } + // for (const { tag, label } of AVAILABLE_TAGS) { + // editor.appendChild(createTagItem(tag, label)); + // } +} + +/** + * @param {string} part + * @return {string} + */ +function getColor(part) { + if (currentSpecies[part]) { + return currentSpecies[part]; + } + if (DEFAULT_OVERRIDES[part]) { + return getColor(DEFAULT_OVERRIDES[part]); + } + for (const [color, partName] of Object.entries(COLOR_MAP)) { + if (partName === part) { + return color; + } + } + return "transparent"; +} + +function createColorPicker() { + colorPickerInput.type = "text"; + colorPickerInput.id = "coloris-proxy"; + colorPickerInput.setAttribute("data-coloris", ""); + document.body.appendChild(colorPickerInput); + + colorPickerInput.addEventListener("input", () => { + if (selectedColorElement && selectedPart !== null) { + const newColor = colorPickerInput.value; + selectedColorElement.style.backgroundColor = newColor; + currentSpecies[selectedPart] = newColor; + draw(); + } + }); + + document.addEventListener("mouseup", () => { + if (selectedPart !== null && !jsonElement.contains(document.activeElement)) { + updateColors(); + } + }); +} + +/** + * @param {string} label + * @param {string} color + * @returns {HTMLDivElement} + */ +function createColorItem(label, color) { + const item = document.createElement("div"); + item.classList.add("editor-item"); + + const colorElement = document.createElement("div"); + colorElement.classList.add("color"); + colorElement.style.backgroundColor = color; + colorElements[label] = colorElement; + item.appendChild(colorElement); + if (color !== "transparent") { + colorElement.addEventListener("click", () => { + selectedPart = label; + selectedColorElement = colorElement; + const rect = colorElement.getBoundingClientRect(); + colorPickerInput.style.left = rect.left + "px"; + colorPickerInput.style.top = (rect.bottom + window.scrollY) + "px"; + + colorPickerInput.value = currentSpecies[label] || color; + colorPickerInput.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + } else { + colorElement.classList.add("color--transparent"); + } + const labelElement = document.createElement("div"); + const labelText = label.replaceAll("-", " ").toUpperCase(); + labelElement.classList.add("label"); + labelElement.textContent = labelText; + labelElement.title = "Click to remove from species"; + labelElement.addEventListener("click", () => { + delete currentSpecies[label]; + colorElement.style.backgroundColor = getColor(label); + updateColors(); + refreshEditorColors(); + }); + item.appendChild(labelElement); + + return item; +} + +/** + * @param {string} tag + * @param {string} label + * @returns {HTMLDivElement} + */ +function createTagItem(tag, label) { + const item = document.createElement("div"); + item.classList.add("tag-item"); + + const toggle = document.createElement("button"); + toggle.classList.add("tag-toggle"); + toggle.textContent = "✓"; + toggle.addEventListener("click", () => { + if (currentTags.has(tag)) { + currentTags.delete(tag); + toggle.classList.remove("tag-toggle--active"); + } else { + currentTags.add(tag); + toggle.classList.add("tag-toggle--active"); + } + draw(); + }); + item.appendChild(toggle); + + const labelElement = document.createElement("div"); + labelElement.classList.add("label"); + labelElement.textContent = label.toUpperCase(); + item.appendChild(labelElement); + + return item; +} + +function refreshEditorColors() { + for (const [, part] of Object.entries(COLOR_MAP)) { + const el = colorElements[part]; + if (el && !el.classList.contains("color--transparent")) { + el.style.backgroundColor = getColor(part); + } + } + if (selectedColorElement && selectedPart !== null) { + colorPickerInput.value = currentSpecies[selectedPart] || ""; + } +} + +function updateJson() { + jsonElement.textContent = JSON.stringify(currentSpecies, null, 2); +} + +document.addEventListener("keydown", (e) => { + if (!(e.metaKey || e.ctrlKey)) { + return; + } + if (e.key === "z" && !e.shiftKey) { + if (historyIndex > 0) { + historyIndex--; + currentSpecies = { ...colorHistory[historyIndex] }; + refreshEditorColors(); + updateJson(); + draw(); + e.preventDefault(); + } + } else if ((e.key === "z" && e.shiftKey) || e.key === "y") { + if (historyIndex < colorHistory.length - 1) { + historyIndex++; + currentSpecies = { ...colorHistory[historyIndex] }; + refreshEditorColors(); + updateJson(); + draw(); + e.preventDefault(); + } + } +}); + +jsonElement.addEventListener("input", () => { + try { + const parsed = JSON.parse(jsonElement.textContent || ""); + if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) { + currentSpecies = parsed; + refreshEditorColors(); + draw(); + } + } catch (e) { + } +}); + +jsonElement.addEventListener("blur", () => { + updateColors(); +}); + +createColorPicker(); +loadEditor(); + +(async () => { + const pixels = await loadSpriteSheetPixels(SPRITE_PATH); + baseFrame = new Frame([ + new Layer(getLayerPixels(pixels, 0, SPRITE_SIZE)), + new Layer(getLayerPixels(pixels, 5, SPRITE_SIZE), TAG.TUFT), + ]); + updateColors(); +})(); \ No newline at end of file diff --git a/editor/index.html b/editor/index.html new file mode 100644 index 0000000..bfed0b5 --- /dev/null +++ b/editor/index.html @@ -0,0 +1,24 @@ + + + + + + + Birb Editor + + + + + + +
+
+ +
+

+		
+
+ + + + \ No newline at end of file diff --git a/editor/stylesheet.css b/editor/stylesheet.css new file mode 100644 index 0000000..9a015ac --- /dev/null +++ b/editor/stylesheet.css @@ -0,0 +1,151 @@ +@import url(https://cdn.jsdelivr.net/npm/firacode@6.2.0/distr/fira_code.css); + +body { + background: linear-gradient(to top, #D2DAE9, white); + height: 100%; + margin: 0; + background-repeat: no-repeat; + background-attachment: fixed; +} + +* { + box-sizing: border-box; +} + +.container { + width: 100vw; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.horizontal-container { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 40px; +} + +#preview { + width: 480px; + height: 480px; + image-rendering: pixelated; + filter: drop-shadow(0px 0px 40px rgba(0, 0, 0, 0.1)); + box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.1); + border-radius: 40px; +} + +#editor { + width: 460px; + height: 480px; + background: #ffffff; + border-radius: 16px; + box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.1); + padding: 20px; + display: flex; + flex-direction: column; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 30px 20px; + column-count: 2; +} + +#json { + width: 200px; + height: 480px; + background: #ffffff; + border-radius: 16px; + box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.1); + padding: 20px; + gap: 20px; + text-align: left; + overflow-x: hidden; + overflow-y: auto; + white-space: pre-wrap; + font-family: 'Fira Code', monospace; + font-size: 12px; +} + +.editor-item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 20px; + height: 38px; +} + +.label { + font-size: 18px; + font-family: 'Fira Code', monospace; + width: 100px; +} + +.color { + width: 32px; + height: 32px; + border-radius: 4px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); + background: red; + transition: transform 0.1s; + cursor: pointer; +} + +.color:hover { + transform: scale(1.15); + transition: 0.1s ease-in; +} + +.color--transparent { + pointer-events: none; + cursor: default; +} + +#coloris-proxy { + position: fixed; + width: 1px; + height: 1px; + opacity: 0; + border: none; + padding: 0; + pointer-events: none; +} + +.tag-item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 20px; + height: 38px; + margin-top: 10px; +} + +.tag-toggle { + width: 32px; + height: 32px; + border-radius: 4px; + border: none; + background: #edf0f4; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); + cursor: pointer; + font-size: 16px; + color: transparent; + transition: background 0.15s, color 0.15s, transform 0.1s; +} + +.tag-toggle:hover { + transform: scale(1.15); + transition: 0.1s ease-in; +} + +.tag-toggle--active { + background: #639bff; + color: white; +} \ No newline at end of file diff --git a/src/animation/sprites.js b/src/animation/sprites.js index 13d49a8..560b002 100644 --- a/src/animation/sprites.js +++ b/src/animation/sprites.js @@ -79,6 +79,62 @@ export class BirdType { } } +/** + * Load a sprite sheet image and convert it to a 2D array of palette color names + * @param {string} src - URL or data URI of the sprite sheet image + * @param {boolean} [templateColors] - Whether to map pixel colors to palette names + * @returns {Promise} + */ +export function loadSpriteSheetPixels(src, templateColors = true) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const pixels = imageData.data; + const hexArray = []; + for (let y = 0; y < img.height; y++) { + const row = []; + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const r = pixels[index]; + const g = pixels[index + 1]; + const b = pixels[index + 2]; + const a = pixels[index + 3]; + if (a === 0) { + row.push(PALETTE.TRANSPARENT); + continue; + } + const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; + if (!templateColors) { + row.push(hex); + continue; + } + if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { + row.push(hex); + continue; + } + row.push(SPRITE_SHEET_COLOR_MAP[hex]); + } + hexArray.push(row); + } + resolve(hexArray); + }; + img.onerror = (err) => { + reject(err); + }; + }); +} + /** @type {Record} */ export const SPECIES = Object.fromEntries( Object.entries(species).map(([id, data]) => [ diff --git a/src/application.js b/src/application.js index 4e80b29..db03045 100644 --- a/src/application.js +++ b/src/application.js @@ -25,7 +25,8 @@ import { import { PALETTE, SPRITE_SHEET_COLOR_MAP, - SPECIES + SPECIES, + loadSpriteSheetPixels, } from './animation/sprites.js'; import { StickyNote, @@ -1202,59 +1203,3 @@ function startApplication(birbPixels, featherPixels, hatsPixels) { draw(); } -/** - * Load the sprite sheet and return the pixel-map template - * @param {string} dataUri - * @param {boolean} [templateColors] - * @returns {Promise} - */ -function loadSpriteSheetPixels(dataUri, templateColors = true) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = dataUri; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const pixels = imageData.data; - const hexArray = []; - for (let y = 0; y < img.height; y++) { - const row = []; - for (let x = 0; x < img.width; x++) { - const index = (y * img.width + x) * 4; - const r = pixels[index]; - const g = pixels[index + 1]; - const b = pixels[index + 2]; - const a = pixels[index + 3]; - if (a === 0) { - row.push(PALETTE.TRANSPARENT); - continue; - } - const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; - if (!templateColors) { - row.push(hex); - continue; - } - if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) { - // Return the color as-is if not found in the map - row.push(hex); - continue; - } - row.push(SPRITE_SHEET_COLOR_MAP[hex]); - } - hexArray.push(row); - } - resolve(hexArray); - }; - img.onerror = (err) => { - reject(err); - }; - }); -} \ No newline at end of file