Last active
March 6, 2023 01:01
-
-
Save FormerlyZeroCool/ee8b24244d8a960d795d486c2bb29dc2 to your computer and use it in GitHub Desktop.
Sprite with RGB dependency
This file contains 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
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}` | |
} | |
}; |
This file contains 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
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