1 MILLION+ COLORS WITHOUT PALETTE CHANGE (technically)

this is not fully completed yet, but this is a simple brightness simulator using rgb colors. it never changes the palette during run time, palette is: “#000000”,“#330000”,“#660000”,“#990000”,“#CC0000”,“#FF0000”,“#003300”,“#006600”,“#009900”,“#00CC00”,“#00FF00”,“#000033”,“#000066”,“#000099”,“#0000CC”,“#0000FF

i am using this to create a game with more than 16 colors. If anybody uses this dont make the screen big because it will lag like crazy. also make sure to pre “dither“ your images, or lag will come.

9 Likes

You can use titlemaps as seen here

And by changing the screen size.
You can also use the colors extension to change the brightness of the rgb colors.

2 Likes

I don’t understand most of this
Very impressive I’ll give you that

3 Likes

Here’s a better example

1 Like

Also i get the importedImage variable through anything that turns an image into raw rgb (im using Photopea), and use this c++ code:

#include <iostream>
#include <fstream>
#include <vector>
#include <array>
#include <string>
#include <algorithm>

struct Color {
    int r, g, b;
};

std::array<Color, 16> PALETTE = { {
    {0x00,0x00,0x00},
    {0x33,0x00,0x00}, {0x66,0x00,0x00}, {0x99,0x00,0x00}, {0xCC,0x00,0x00}, {0xFF,0x00,0x00},
    {0x00,0x33,0x00}, {0x00,0x66,0x00}, {0x00,0x99,0x00}, {0x00,0xCC,0x00}, {0x00,0xFF,0x00},
    {0x00,0x00,0x33}, {0x00,0x00,0x66}, {0x00,0x00,0x99}, {0x00,0x00,0xCC}, {0x00,0x00,0xFF}
} };

// ---------------------
//  Closest palette color
// ---------------------
int closest(int r, int g, int b) {
    int best = 0;
    long bestDist = LONG_MAX;

    for (int i = 0; i < 16; i++) {
        int dr = PALETTE[i].r - r;
        int dg = PALETTE[i].g - g;
        int db = PALETTE[i].b - b;
        long dist = dr * dr + dg * dg + db * db;

        if (dist < bestDist) {
            bestDist = dist;
            best = i;
        }
    }
    return best;
}

// ---------------------
//  rgbToNumberArr (C++ version)
// ---------------------
std::array<int, 9> rgbToNumberArr(int r, int g, int b) {
    double R = r, G = g, B = b;
    std::array<std::string, 9> pattern;

    auto toHexByte = [](int n) {
        const char* hex = "0123456789ABCDEF";
        n = std::max(0, std::min(255, n));
        std::string s;
        s += hex[(n >> 4) & 0xF];
        s += hex[n & 0xF];
        return s;
        };

    auto decode = [](const std::string& code) {
        char c = code[0];
        int v = std::stoi(code.substr(1), nullptr, 16);
        if (c == 'R') return Color{ v,0,0 };
        if (c == 'G') return Color{ 0,v,0 };
        return Color{ 0,0,v };
        };

    // Generate 9-pixel channel pattern
    for (int i = 0; i < 9; i++) {
        char channel = 'R';
        double value = R;

        if (G >= R && G >= B) { channel = 'G'; value = G; }
        else if (B >= R && B >= G) { channel = 'B'; value = B; }

        pattern[i] = std::string(1, channel) + toHexByte((int)value);

        if (channel == 'R') R -= r / 9.0;
        if (channel == 'G') G -= g / 9.0;
        if (channel == 'B') B -= b / 9.0;
    }

    // Convert to palette indices
    std::array<int, 9> out;
    for (int i = 0; i < 9; i++) {
        Color px = decode(pattern[i]);
        out[i] = closest(px.r, px.g, px.b);
    }

    return out;
}

// ---------------------
//  MAIN: RAW -> number[x][y][c]
// ---------------------
int main() {
    const int WIDTH = 144;   // <-- set your width
    const int HEIGHT = 81;  // <-- set your height

    // Load RAW file
    std::ifstream file("image.raw", std::ios::binary);
    std::vector<unsigned char> bytes(
        (std::istreambuf_iterator<char>(file)),
        std::istreambuf_iterator<char>()
    );

    if (bytes.size() != WIDTH * HEIGHT * 3) {
        std::cout << "ERROR: RAW file size does not match WIDTH*HEIGHT*3\n";
        return 1;
    }

    // Convert to pixel array
    std::vector<std::vector<Color>> pixels(HEIGHT, std::vector<Color>(WIDTH));
    int idx = 0;

    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            pixels[y][x] = {
                bytes[idx++], // R
                bytes[idx++], // G
                bytes[idx++]  // B
            };
        }
    }

    // Convert to GPU tiles: number[x][y][c]
    std::vector<std::vector<std::array<int, 9>>> number(
        HEIGHT, std::vector<std::array<int, 9>>(WIDTH)
    );

    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            Color c = pixels[y][x];
            number[y][x] = rgbToNumberArr(c.r, c.g, c.b);
        }
    }
	



    std::ofstream outFile("output.txt");
    if (!outFile.is_open()) {
        std::cout << "Failed to open output.txt\n";
        return 1;
    }

    outFile << "let importedImage: number[][][] = [\n";

    for (int x = 0; x < WIDTH; x++) {
        outFile << "    [\n";

        for (int y = 0; y < HEIGHT; y++) {
            outFile << "        [";

            for (int c = 0; c < 9; c++) {
                outFile << number[y][x][c];
                if (c != 8) outFile << ", ";
            }

            outFile << "]";
            if (y != HEIGHT - 1) outFile << ",";
            outFile << "\n";
        }

        outFile << "    ]";
        if (x != WIDTH - 1) outFile << ",";
        outFile << "\n";
    }

    outFile << "];\n";
    outFile.close();

    std::cout << "Wrote output.txt successfully.\n";


    return 0;
}

note that you will need to change the PALETTE variable if you have a different palette in your makecode project

3 Likes

yes but if i do tilemaps i cant do the dithering to anything, and im trying to make… Celeste? actually i may do tilemaps theres a chance

2 Likes

northern lights (test):

8 Likes

Yo that’s crazy! I tried to do something like this once, but I never got an actual image.

3 Likes

I have one question:
should I post what I am doing every now and then, or should I work until it is completed?

3 Likes

Every now and then, I love seeing halfway done projects.

1 Like

made Madeline’s walk animation, and the hair (not part of the animation because Celeste does hair physics) though the hair doesnt have the exact physics in this example. if the project lags, open the code and at the top read the comment

3 Likes

Hey I’m making a Fnaf 6 Style game and I was curious if its possible to integrate Salvage sections with this extension?

I just saw it and thought “You know that looks like that style could transfer over fairly well” Just because the salvage sections are gonna have a more realistic art style, and there also gonna have pre-rendered assets.

Of course I’m not asking you to do it for me, I’m just curious on if you think something like that could work with this extension.

1 Like

@miles what Danny’s tryna say is we can figure out the mechanics, but would it be possible to use this extension for the visuals of a salvage section like that from Fnaf 6? we just need a greater understanding of what this is capable of.

:ᗡ I completely forgot about this!!

what I was going to say:

if this helps, these are explanations of things in “madeline + incorrect hair“:

Explanation of entire project

format of each “rgb pixel” is a number[] of size 9, each number is a palette index:
the palette is 000000, 330000, 660000, 990000, CC0000, FF0000, 003300, 006600, 009900, 00CC00, 00FF00, 000033, 000066, 000099, 0000CC, 0000FF, basically rgb with 6 evenly spaced brightnesses, 00, 33, 66, 99 CC, FF
one image in this format is number[x][y][c]
hair is generated using verlet rope (i think)

GPU.ts
class CelesteGPU

the custom GPU to handle number[]’s

constructor(scale: number)

inits the gpu
scale - the scale of each visual pixel

colorsToTile(colors: number\[\])

converts a number[] (visual color) into an Image based on palette indexes
colors - the input visual pixel to convert
returns a 3x3 Image

clear()

“clears” the screen, or makes every fb[x][y] equal to [0, 0, 0, 0, 0, 0, 0, 0, 0] (clear pixel)

render()

“renders” the sreen, or turns each fb[x][y] into an Image (using colorsToTile()) and draws it

loadFromCelesteData()

converts allColors, allTiles, and tilemap0 into number[][][], and sets fb to that

shuffle()

“shuffles” each visual pixel of fb (using shuffleTile(colors))

shuffleTile(colors: number\[\])

“shuffles” colors using the fisher yachts algorithm

setPixel(x: number, y: number, c: number\[\])

sets fb[x][y] to c
x - color to set at x
y - color to set at y
c - color to set to

drawImage(img: number\[\]\[\]\[\], x: number, y: number)

“draws” and image to fb, similar to Image.prototype.drawImage(Image, x, y)
img - image to draw
x - image top draw at x
y - image top draw at y

drawOpaqueImage(img: number\[\]\[\]\[\], x: number, y: number)

“draws” every pixel in img except the clear pixels to fb, similar to Image.prototype.drawImage(Image, x, y)
img - image to draw
x - image top draw at x
y - image top draw at y

hair.ts
class Madeline

an “entity”, helps simplify things

constructor(x: number, y: number)

inits Madeline
x - Madeline’s x position
y - Madeline’s y position

class HairNode

helps simplify verlet rope

constructor()

inits the HairNode

loading.ts
scaleImage(src: number\[\]\[\]\[\], scale: number)

scales a number[][][] by scale
src - source number[][][] to scale
scale - scale size
returns a scaled number[][][]

rgbToNumberArr(r: number, g: number, b: number)

converts rgb color to a visually accurate number[]
r - color’s r valus
g - color’s g valus
b - color’s b valus
returns a visually accurate number[]

class PNGData

helper class

constructor(w: number, h: number, x: number, y: number, data: string)

inits the PNGData
w - width of png
h - height of png
x - x offset of png (used to trim the image used to compress png’s even more)
y - y position of png (used to trim the image used to compress png’s even more)
data - pixel data of png

loadPngAnim(PNGs: (string | number)\[\]\[\])

loads an array of png’s into IMGs
PNGs - array of PNGs to load

main.ts
game.onUpdateInterval(50, function() {})

verlet rope physics
sets fb to IMGs[n] based on how many times it has triggered

game.onPaint(function () {))

renders to the screen using CelesteGPU.prototype.render()

hope that helps :smiley:

what I'm saying now

I have never played fnaf, so I may watch a playthrough to understand the graphics better.

Right now, one visual pixel is a 3x3, and a number[] (NOT a number[][]) (should always have a size of 9) resembles it. visualPixel[n] is a palette index. the gpu also has a shuffleTile() function to randomly shuffle a given number[]

the gpu’s render() should be in game.onpaint(). it renders the screen, using colorsToTile() (number[] → Image)

gpu.fb is a number[][][] and stores the screens pixels, and gpu.fb[x][y] is a position on the screen, returning a visual pixel (number[])

drawImage draws a number[][][] (same thing as render() but with an input to render rather than fb) and drawOpaqueImage() draws everything in the image OTHER THAN the black tiles ([0,0,0,0,0,0,0,0,0])

just please note that nothing in the project is an “extension”, it’s just that they are made in different files. I made this for king_bob, and it has everything in one file: https://arcade.makecode.com/S90433-28001-45589-11727. note that huge screens (like 300x300 or bigger maybe) can slow things down alot, especially gpu.shuffle() (shuffles the INTIRE gpu.fb). to speed things up, make sure to parse each number[][][] into an Image.

BTW, I have some c++ code used to export my png’s into something I can copy+paste into makecode, but I don’t know anything about getting it on github. how do I do it on a way that people can easily use it?

2 Likes

what if I linked base game with all the coding complete (with placeholder assets) and linked the real assets so from there you could adapt them? the game doesn’t use a ton of assets (less then ten) so I don’t think it would be that hard. unless it is.

1 Like

if you mean that I turn an image file (I don’t know what files types work, but png does) into a number[][][], I could do that. I’m also trying to get my new c++ converter (well technically its copilot’s) on github so everybody can use it

1 Like

yeah that’s what I meant. I’ll link em here once I’m done.

1 Like

Can you turn this into an extension?

yes, I can try, though first I want to finish an audio exporter