Skip to content

Instantly share code, notes, and snippets.

@colecrouter
Last active March 20, 2022 07:48
Show Gist options
  • Select an option

  • Save colecrouter/3fa32fcff057bb39b44e8baed1f2529a to your computer and use it in GitHub Desktop.

Select an option

Save colecrouter/3fa32fcff057bb39b44e8baed1f2529a to your computer and use it in GitHub Desktop.

Revisions

  1. colecrouter revised this gist Jan 13, 2022. No changes.
  2. colecrouter revised this gist Jan 13, 2022. No changes.
  3. colecrouter revised this gist Jan 13, 2022. No changes.
  4. colecrouter created this gist Jan 13, 2022.
    94 changes: 94 additions & 0 deletions preview.png.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,94 @@
    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;
    };