Skip to content

Instantly share code, notes, and snippets.

@FormerlyZeroCool
Last active March 6, 2023 01:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FormerlyZeroCool/ee8b24244d8a960d795d486c2bb29dc2 to your computer and use it in GitHub Desktop.
Save FormerlyZeroCool/ee8b24244d8a960d795d486c2bb29dc2 to your computer and use it in GitHub Desktop.
Sprite with RGB dependency
export function blendAlphaCopy(color0:RGB, color:RGB):void
{
const alphant:number = color0.alphaNormal();
const alphanc:number = color.alphaNormal();
const a:number = (1 - alphanc);
const a0:number = (alphanc + alphant * a);
const a1:number = 1 / a0;
color0.color = (((alphanc * color.red() + alphant * color0.red() * a) * a1)) |
(((alphanc * color.green() + alphant * color0.green() * a) * a1) << 8) |
(((alphanc * color.blue() + alphant * color0.blue() * a) * a1) << 16) |
((a0 * 255) << 24);
/*this.setRed ((alphanc*color.red() + alphant*this.red() * a ) *a1);
this.setBlue ((alphanc*color.blue() + alphant*this.blue() * a) *a1);
this.setGreen((alphanc*color.green() + alphant*this.green() * a)*a1);
this.setAlpha(a0*255);*/
}
export class RGB {
color:number;
constructor(r:number = 0, g:number = 0, b:number, a:number = 0)
{
this.color = 0;
this.color = a << 24 | b << 16 | g << 8 | r;
}
blendAlphaCopy(color:RGB):void
{
blendAlphaCopy(this, color);
/*this.setRed ((alphanc*color.red() + alphant*this.red() * a ) *a1);
this.setBlue ((alphanc*color.blue() + alphant*this.blue() * a) *a1);
this.setGreen((alphanc*color.green() + alphant*this.green() * a)*a1);
this.setAlpha(a0*255);*/
}
toHSL():number[]//[hue, saturation, lightness]
{
const normRed:number = this.red() / 255;
const normGreen:number = this.green() / 255;
const normBlue:number = this.blue() / 255;
const cMax:number = Math.max(normBlue, normGreen, normRed);
const cMin:number = Math.min(normBlue, normGreen, normRed);
const delta:number = cMax - cMin;
let hue:number = 0;
if(delta !== 0)
{
if(cMax === normRed)
{
hue = 60 * ((normGreen - normBlue) / delta % 6);
}
else if(cMax === normGreen)
{
hue = 60 * ((normBlue - normRed) / delta + 2);
}
else
{
hue = 60 * ((normRed - normGreen) / delta + 4);
}
}
const lightness:number = (cMax + cMin) / 2;
const saturation:number = delta / (1 - Math.abs(2*lightness - 1));
return [hue, saturation, lightness];
}
setByHSL(hue:number, saturation:number, lightness:number): void
{
const c:number = (1 - Math.abs(2 * lightness - 1)) * saturation;
const x:number = c * (1 - Math.abs(hue / 60 % 2 - 1));
const m:number = lightness - c / 2;
if(hue < 60)
{
this.setRed((c + m) * 255);
this.setGreen((x + m) * 255);
this.setBlue(0);
}
else if(hue < 120)
{
this.setRed((x + m) * 255);
this.setGreen((c + m) * 255);
this.setBlue(m * 255);
}
else if(hue < 180)
{
this.setRed(m * 255);
this.setGreen((c + m) * 255);
this.setBlue((x + m) * 255);
}
else if(hue < 240)
{
this.setRed(0);
this.setGreen((x + m) * 255);
this.setBlue((c + m) * 255);
}
else if(hue < 300)
{
this.setRed((x + m) * 255);
this.setGreen(m * 255);
this.setBlue((c + m) * 255);
}
else
{
this.setRed((c + m) * 255);
this.setGreen(m * 255);
this.setBlue((x + m) * 255);
}
this.setAlpha(255);
}
compare(color:RGB):boolean
{
return color && this.color === color.color;
}
copy(color:RGB):void
{
this.color = color.color;
}
toInt():number
{
return this.color;
}
toRGBA():Array<number>
{
return [this.red(), this.green(), this.blue(), this.alpha()]
}
alpha():number
{
return (this.color >> 24) & ((1<<8)-1);
}
blue():number
{
return (this.color >> 16) & ((1 << 8) - 1);
}
green():number
{
return (this.color >> 8) & ((1 << 8) - 1);
}
red():number
{
return (this.color) & ((1 << 8) - 1);
}
alphaNormal():number
{
return Math.round((((this.color >> 24) & ((1<<8)-1)) / 255)*100)/100;
}
setAlpha(red:number)
{
this.color &= (1<<24)-1;
this.color |= red << 24;
}
setBlue(green:number)
{
this.color &= ((1 << 16) - 1) | (((1<<8)-1) << 24);
this.color |= green << 16;
}
setGreen(blue:number)
{
this.color &= ((1<<8)-1) | (((1<<16)-1) << 16);
this.color |= blue << 8;
}
setRed(alpha:number)
{
this.color &= (((1<<24)-1) << 8);
this.color |= alpha;
}
loadString(color:string):number
{
try {
let r:number
let g:number
let b:number
let a:number
if(color.substring(0,4).toLowerCase() !== "rgba"){
if(color[0] !== "#")
throw new Error("Exception malformed color: " + color);
r = parseInt(color.substring(1,3), 16);
g = parseInt(color.substring(3,5), 16);
b = parseInt(color.substring(5,7), 16);
a = parseFloat(color.substring(7,9))*255;
}
else
{
const vals = color.split(",");
vals[0] = vals[0].split("(")[1];
vals[3] = vals[3].split(")")[0];
r = parseInt(vals[0], 10);
g = parseInt(vals[1], 10);
b = parseInt(vals[2], 10);
a = parseFloat(vals[3])*255;
}
let invalid:number = 0;
if(!isNaN(r) && r >= 0)
{
if(r > 255)
{
this.setRed(255);
invalid = 2;
}
else
this.setRed(r);
}
else
invalid = +(r > 0);
if(!isNaN(g) && g >= 0)
{
if(g > 255)
{
this.setGreen(255);
invalid = 2;
}
else
this.setGreen(g);
}
else
invalid = +(g > 0);
if(!isNaN(b) && b >= 0)
{
if(b > 255)
{
this.setBlue(255);
invalid = 2;
}
else
this.setBlue(b);
}
else
invalid = +(b > 0);
if(!isNaN(a) && a >= 0)
{
if(a > 255)
{
this.setAlpha(255);
invalid = 2;
}
else
this.setAlpha(a);
}
else
invalid = +(a > 0);
if(color[color.length - 1] !== ")")
invalid = 1;
let openingPresent:boolean = false;
for(let i = 0; !openingPresent && i < color.length; i++)
{
openingPresent = color[i] === "(";
}
if(!openingPresent)
invalid = 1;
return invalid;
} catch(error:any)
{
console.log(error);
return 0;
}
}
htmlRBGA():string{
return `rgba(${this.red()}, ${this.green()}, ${this.blue()}, ${this.alphaNormal()})`
}
htmlRBG():string{
const red:string = this.red() < 16?`0${this.red().toString(16)}`:this.red().toString(16);
const green:string = this.green() < 16?`0${this.green().toString(16)}`:this.green().toString(16);
const blue:string = this.blue() < 16?`0${this.blue().toString(16)}`:this.blue().toString(16);
return `#${red}${green}${blue}`
}
};
export class Sprite {
pixels:Uint8ClampedArray;
imageData:ImageData | null;
image:HTMLCanvasElement;
ctx:CanvasRenderingContext2D;
fillBackground:boolean;
width:number;
height:number;
constructor(pixels:Array<RGB>, width:number, height:number, fillBackground:boolean = false)
{
this.fillBackground = fillBackground;
this.imageData = null;
this.pixels = null;
this.image = document.createElement("canvas");
this.ctx = this.image.getContext("2d", {desynchronized:true})!;
this.width = width;
this.height = height;
if(width * height > 0)
this.copy(pixels, width, height);
}
copyCanvas(canvas:HTMLCanvasElement):void
{
this.width = canvas.width;
this.height = canvas.height;
this.image.width = this.width;
this.image.height = this.height;
this.ctx = this.image.getContext("2d")!;
this.ctx.imageSmoothingEnabled = false;
this.ctx.drawImage(canvas, 0, 0);
this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
this.pixels = this.imageData.data;
}
flipHorizontally(): void
{
let left:RGB = new RGB(0,0,0,0);
let right:RGB = new RGB(0,0,0,0);
for(let y = 0; y < this.height; y++)
{
const yOffset:number = y * this.width;
for(let x = 0; x < this.width << 1; x++)
{
left.color = this.pixels[x + yOffset];
right.color = this.pixels[yOffset + (this.width - 1) - x];
if(left && right)
{
const temp:number = left.color;
left.copy(right);
right.color = temp;
}
}
}
this.refreshImage();
}
copyImage(image:HTMLImageElement):void
{
this.width = image.width;
this.height = image.height;
this.image.width = this.width;
this.image.height = this.height;
this.ctx = this.image.getContext("2d")!;
this.ctx.imageSmoothingEnabled = false;
this.ctx.drawImage(image, 0, 0);
this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
this.pixels = this.imageData.data;
}
createImageData():ImageData {
const canvas = this.image;
if(canvas.width !== this.width || canvas.height !== this.height)
{
canvas.width = this.width;
canvas.height = this.height;
}
this.ctx = canvas.getContext('2d')!;
this.ctx.imageSmoothingEnabled = false;
return this.ctx.createImageData(this.width, this.height);
}
copy(pixels:Array<RGB>, width:number, height:number):void
{
this.width = width;
this.height = height;
if(width !== 0 && height !== 0)
{
if(!this.pixels || this.pixels.length !== pixels.length || this.pixels.length > 0)
{
this.imageData = this.createImageData();
this.pixels = this.imageData.data;
}
const view:Int32Array = new Int32Array(this.pixels.buffer)
for(let i = 0; i < pixels.length; i++)
{
view[i] = pixels[i].color;
}
if(pixels.length)
this.refreshImage();
}
}
putPixels(ctx:CanvasRenderingContext2D):void
{
if(this.imageData)
ctx.putImageData(this.imageData, 0, 0);
}
fillRect(color:RGB, x:number, y:number, width:number, height:number, view:Int32Array = new Int32Array(this.pixels.buffer))
{
for(let yi = y; yi < y+height; yi++)
{
const yiIndex:number = (yi*this.width);
const rowLimit:number = x + width + yiIndex;
for(let xi = x + yiIndex; xi < rowLimit; xi++)
{
view[xi] = color.color;
}
}
}
fillRectAlphaBlend(source:RGB, color:RGB, x:number, y:number, width:number, height:number, view:Int32Array = new Int32Array(this.pixels.buffer))
{
for(let yi = y; yi < y+height; yi++)
{
for(let xi = x; xi < x+width; xi++)
{
let index:number = (xi) + (yi*this.width);
source.color = view[index];
source.blendAlphaCopy(color);
view[index] = source.color;
}
}
}
copyToBuffer(buf:Array<RGB>, width:number, height:number, view:Int32Array = new Int32Array(this.pixels.buffer))
{
if(width * height !== buf.length)
{
console.log("error invalid dimensions supplied");
return;
}
for(let y = 0; y < this.height && y < height; y++)
{
for(let x = 0; x < this.width && x < width; x++)
{
const i:number = (x + y * width);
const vi:number = x + y * this.width;
buf[i].color = view[vi];
}
}
}
binaryFileSize():number
{
return 3 + this.width * this.height;
}
saveToUint32Buffer(buf:Int32Array, index:number, view:Int32Array = new Int32Array(this.pixels.buffer)):number
{
buf[index++] = this.binaryFileSize();
buf[index++] = 3;
buf[index] |= this.height << 16;
buf[index++] |= this.width;
for(let i = 0; i < view.length; i++)
{
buf[index] = view[i];
index++;
}
return index;
}
refreshImage():void
{
const canvas = this.image;
if(canvas.width !== this.width || canvas.height !== this.height)
{
canvas.width = this.width;
canvas.height = this.height;
this.ctx = canvas.getContext("2d")!;
}
this.putPixels(this.ctx);
}
copySprite(sprite:Sprite):void
{
this.width = sprite.width;
this.height = sprite.height;
this.imageData = this.createImageData();
this.pixels = this.imageData.data;
for(let i = 0; i < this.pixels.length;)
{
this.pixels[i] = sprite.pixels[i++];
this.pixels[i] = sprite.pixels[i++];
this.pixels[i] = sprite.pixels[i++];
this.pixels[i] = sprite.pixels[i++];
}
}
copySpriteBlendAlpha(sprite:Sprite):void
{
if(this.pixels.length !== sprite.pixels.length){
this.imageData = this.createImageData();
this.pixels = this.imageData.data;
}
this.width = sprite.width;
this.height = sprite.height;
const o:RGB = new RGB(0, 0, 0, 0);
const t:RGB = new RGB(0, 0, 0, 0);
for(let i = 0; i < this.pixels.length; i += 4)
{
o.setRed(sprite.pixels[i]);
o.setGreen(sprite.pixels[i+1]);
o.setBlue(sprite.pixels[i+2]);
o.setAlpha(sprite.pixels[i+3]);
t.setRed(this.pixels[i]);
t.setGreen(this.pixels[i+1]);
t.setBlue(this.pixels[i+2]);
t.setAlpha(this.pixels[i+3]);
t.blendAlphaCopy(o);
this.pixels[i] = t.red();
this.pixels[i+1] = t.green();
this.pixels[i+2] = t.blue();
this.pixels[i+3] = t.alpha();
}
}
draw(ctx:CanvasRenderingContext2D, x:number, y:number, width:number, height:number):void
{
if(this.pixels){
if(this.fillBackground){
ctx.clearRect(x, y, width, height);
}
ctx.drawImage(this.image, x, y, width, height);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment