Last active
March 20, 2022 07:48
-
-
Save colecrouter/3fa32fcff057bb39b44e8baed1f2529a to your computer and use it in GitHub Desktop.
ES6 janky png editing NO CANVAS Cloudflare Workers/Pages Functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Inventory, Item, SharedKit } from '../types/kit'; | |
import { decode, encode, toRGBA8 } from 'upng-js'; | |
let MCTEXTURES: Textures; | |
const INV_WIDTH = 181; | |
const INV_HEIGHT = 115; | |
const ITEM_WIDTH = 32; | |
const ITEM_HEIGHT = 32; | |
export const onRequestGet = async ({ request, env, next, data }) => { | |
const url = new URL(request.url); | |
const param = url.search; | |
const subject = param.substring(1); | |
// Loading textures, get kit data at the same time | |
let kitData: SharedKit; | |
let texturePromise = fetch("[redacted]").then((res) => res.json()); | |
let kitPromise = fetch(`[redacted]`, { headers: { "Authorization": `Bearer ${env.API_KEY}` } }).then(res => res.json()); | |
await Promise.all([texturePromise, kitPromise]).then(([textures, kit]) => { | |
MCTEXTURES = textures; | |
kitData = kit; | |
}); | |
// Do the thing | |
const encoded = await paintInventory(kitData.data.inventory); | |
return new Response(encoded, { status: 200, headers: { "content-type": "image/png", "cache-control": "public", "expires": new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toUTCString() /* One week */ } }); | |
}; | |
const paintInventory = async (inv: Inventory) => { | |
const imposeImage = async (bgLayer: ArrayBuffer, fg: Item | undefined, x: number, y: number): Promise<Uint8Array> => { | |
let bgView = new Uint8Array(bgLayer); // Convert our buffer to a view. We need to do this inside our function for some reason, otherwise it becomes undefined? | |
const fgBase64 = itemToBase64(fg); | |
if (fgBase64 === undefined) { return; } | |
const fgBuffer = await base64ToBuffer(fgBase64); | |
const fgDecoded = decode(fgBuffer); // Convert our item to a buffer | |
const fgLayer = toRGBA8(fgDecoded)[0]; // Decode our buffer to RGBA | |
const fgView = new Uint8Array(fgLayer); // Convert our buffer to a view | |
// Literally don't try to even understand this block of code, I have no idea how I did it or how I made it work. | |
x++, y--; // They don't come out aligned for some reason | |
for (let i = 0; i < 1024; i += 4) { | |
// For some reason, the icons are 32x32 instead of 16x16, so I'm doing my best to downscale them in one for loop. This would be 100x simpler if they were the right size. | |
const rowOffset = Math.floor((i) / (ITEM_WIDTH / 2 * 4)) /* Current row number */ * ((INV_WIDTH - (ITEM_WIDTH / 2)) * 4 /* How many pixels until new line*/) + (x * 4) /* X offset */ + (y * (INV_WIDTH * 4)); /* Y offset */ | |
const columnOffset = Math.floor((i / 2) / ITEM_WIDTH) * (ITEM_WIDTH * 2) /* Current column number */; | |
// Transparency hack | |
if (fgView[((i + columnOffset) * 2) + 3] === 0) { continue; } | |
try { | |
// Set all four channels | |
bgView[(i) + rowOffset] = fgView[((i + columnOffset) * 2)]; | |
bgView[(i + 1) + rowOffset] = fgView[((i + columnOffset) * 2) + 1]; | |
bgView[(i + 2) + rowOffset] = fgView[((i + columnOffset) * 2) + 2]; | |
bgView[(i + 3) + rowOffset] = fgView[((i + columnOffset) * 2) + 3]; | |
} catch (err) { } | |
} | |
}; | |
const base64ToBuffer = async (base64: string): Promise<ArrayBuffer> => { | |
const byteString = atob(base64.split(',')[1]); // Remove "data:image/png;base64,", then decode into binary string | |
const ab = new ArrayBuffer(byteString.length); // Create ArrayBuffer with the length of the decoded base64 string | |
let ia = new Uint8Array(ab); // Create a view of ab. For more info on views ArrayBuffers, see https://javascript.info/arraybuffer-binary-arrays | |
// Fill the ArrayBuffer with the decoded base64 string | |
for (var i = 0; i < byteString.length; i++) { | |
ia[i] = byteString.charCodeAt(i); | |
} | |
return ab; // Return the updated ArrayBuffer, since the view is linked to it, and it is therefore updated | |
}; | |
// Helper to get the base64 texture of an Item | |
const itemToBase64 = (item: Item): string | undefined => { | |
return MCTEXTURES.items.find((el) => el.id === `minecraft:${item?.material.toLowerCase()}`)?.texture; | |
}; | |
// Make inventory image | |
//const b64 = itemToBase64(json.data.inventory.head as Item); | |
const bgBase64 = "[redacted]"; | |
const bgBuffer = await base64ToBuffer(bgBase64); | |
const bgDecoded = decode(bgBuffer); // Convert our item to a buffer | |
const bgLayer = toRGBA8(bgDecoded)[0]; // Decode our buffer to RGBA | |
// Add image | |
await imposeImage(bgLayer, inv.offhand, 9 + (5 * 18), 10); | |
// Return image | |
let newImg = encode([bgLayer], INV_WIDTH, INV_HEIGHT, 0); // We insert img instead of imgView because imgView is linked to img | |
return newImg; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment