wow, this effect looks AMAZING! just perused your code a bit and you did an awesome job!
iāve got some tips/suggestions which you can choose to take or leave
Use the //% whenUsed annotation
whenever you declare a variable inside the top level of a namespace (that is to say, inside a namespace but not inside a function), you should add a //% whenUsed annotation above it. this annotation tells the compiler to only include that variable in the code if itās actually reference in the userās program.
for example, you could add it to each of the variables here
Cache colors to speed things up
the work youāre doing here is pretty expensive since itās being done once per pixel for every transparent sprite on every frame. if you want to speed things up, iād recommend caching the ideal color for each color combo so that you donāt have to constantly recalculate the lowest distance.
hereās an idea for how to write a function that caches the color:
let colorCache = control.createBuffer(256);
function lookupColor(spriteColorIndex: number, backgroundColorIndex: number) {
const cacheIndex = (spriteColorIndex * 16) + backgroundColorIndex;
if (colorCache[cacheIndex] === 0) {
colorCache[cacheIndex] = calculateLowestDistanceColor(spriteColorIndex, backgroundColorIndex)
}
return colorCache[cacheIndex]
}
A handy bitshifting trick
looks like youāre doing a lot of work here to extract the red, green, and blue values of each color in the palette. thereās actually a much easier way to extract those numbers using bitshifting!
note: bitshifting can be a little tough to wrap your head around if you arenāt used to thinking about bytes/numbers in terms of 1ās and 0ās. if you donāt understand the comments in the code below, donāt worry about it! i think this is a concept that is much easier to understand with some diagrams/images that explain things
// integers are 32 bits, meaning they contain 4 bytes of data. the number
// returned by p.color() uses the lower 3 bytes of that data to store the
// red, green, and blue color values for the palette color.
//
// it looks like this:
// byte 1 (bits 0-7) is the BLUE channel
// byte 2 (bits 8-15) is the GREEN channel
// byte 3 (bits 16-23) is the RED channel
// byte 4 (bits 24-31) is unused (all 0s)
//
// or, if we were to write out the individual bits, it would look like
// this:
// 0000 0000 RRRR RRRR GGGG GGGG BBBB BBBB
//
// note that we're writing this from highest bit to lowest bit, so all the
// blue bits are on the right of our number!
function unpackColor(color: number) {
// first, let's extract our blue value, to do that we want to grab the
// lowest byte. luckily, there's an easy way to do this using the bitwise & operator!
//
// the & operator takes all the bits in the left and right integers and returns
// a number where only the bits that are 1 in both inputs are set.
//
// for example: 01010101 & 00001111 = 00000101
// there are only two bits that are 1 in both numbers, so those are the only
// ones set in the result!
//
// another way of thinking about the & operator is that you can use the right
// number to "filter" out bits from the left number. if we want to get the lower
// 8 bits of our number, we just need to & it with something that has only the lower
// 8 bits set to 1!
//
// in other words:
// 0000 0000 RRRR RRRR GGGG GGGG BBBB BBBB &
// 0000 0000 0000 0000 0000 0000 1111 1111 =
// 0000 0000 0000 0000 0000 0000 BBBB BBBB
//
// we can write out that number with only the lowest 8 bits set as 0xff
const blue = color & 0xff
// the right shift operator ">>" shifts all the bits in a number
// to the right by the number you specify. all of the bits that
// get shifted past the lowest bit are chopped off and replaced with
// 0s on the left side.
//
// for example: 0110 1001 >> 4 = 0000 0110
// we shifted the upper 4 bits (0110) to the right and the lower 4 bits
// were chopped off!
//
// to get our green value, we can move all of our bytes "down" by shifting
// them to the right by a multiple of 8. in this case, we want to move down 1
// byte, so we shift to the right by 8.
//
// just like with our blue value, we also use & 0xff to chop off everything but
// the lowest 8 bits
const green = (color >> 8) & 0xff
// finally, we can get the red byte by shifting "down" two bytes (aka 16 bits).
// for this one, we don't need to bother using the & operator since we know that
// all the bits above the red byte are already 0s
const red = color >> 16;
return [red, green, blue]
}
Getting the color underneath a pixel
i like the trick you did here for getting the color underneath a pixel! however, as iām sure youāve realized, this wonāt work for sprites on top of other sprites. if thatās a scenario youāre interested in supporting, then iāve got an idea you can use to do it!
my arcade-drawing-utils extension has an API that can be used to add code that gets called right before a sprite is drawn to the screen. using that, you can read the background colors directly off of the screen image!
drawing.renderOnSprite(mySprite, drawing.RenderOrder.Below, () => {
// don't actually draw anything here, just update the transparent image.
// this code will run as the screen is being rendered, so instead of your
// getColor(x, y) function, you can just use screen.getPixel(x, y)
})