492 lines
13 KiB
TypeScript
492 lines
13 KiB
TypeScript
class Crusher {
|
|
hash(s: any): number {
|
|
return String(s).split("").reduce(function(a, b) {
|
|
a = ((a << 5) - a) + b.charCodeAt(0);
|
|
return a & a
|
|
}, 0);
|
|
}
|
|
isDOMElement(o:any): boolean {
|
|
return (
|
|
typeof HTMLElement === "object" ? o instanceof HTMLElement :
|
|
o && typeof o === "object" && true && o.nodeType === 1 && typeof o.nodeName==="string"
|
|
);
|
|
}
|
|
}
|
|
|
|
enum BlockType {
|
|
ONE = 1,
|
|
TWO = 2,
|
|
THREE = 3,
|
|
}
|
|
|
|
class Block {
|
|
canvas: HTMLCanvasElement;
|
|
context: CanvasRenderingContext2D;
|
|
type: BlockType;
|
|
primary: string;
|
|
accent: string;
|
|
cellSize: number;
|
|
margin: number;
|
|
scale: number;
|
|
pos: Position;
|
|
hash: number;
|
|
|
|
constructor(
|
|
c: HTMLCanvasElement,
|
|
ctx: CanvasRenderingContext2D,
|
|
type: BlockType,
|
|
primary: string,
|
|
accent: string,
|
|
hash: number,
|
|
cellSize: number,
|
|
margin: number,
|
|
scale: number,
|
|
pos: Position,
|
|
) {
|
|
this.canvas = c;
|
|
this.context = ctx;
|
|
this.type = type;
|
|
this.primary = primary;
|
|
this.accent = accent;
|
|
this.hash = hash;
|
|
this.cellSize = cellSize;
|
|
this.margin = margin;
|
|
this.scale = scale;
|
|
this.pos = pos;
|
|
}
|
|
|
|
offset() {
|
|
this.context.save();
|
|
this.context.translate(0.6 * this.scale, -0.6 * this.scale);
|
|
}
|
|
|
|
resetOffset() {
|
|
this.context.restore();
|
|
}
|
|
|
|
makePath(hash: number, offset: number) {
|
|
const mod = Math.abs(hash + offset) % 4;
|
|
|
|
switch(mod) {
|
|
case 0:
|
|
// top
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y);
|
|
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize);
|
|
this.context.closePath();
|
|
break;
|
|
case 1:
|
|
// right
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x + this.cellSize, this.pos.y);
|
|
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize);
|
|
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
|
|
this.context.closePath();
|
|
break;
|
|
case 2:
|
|
// bottom
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
|
|
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize);
|
|
this.context.closePath();
|
|
break;
|
|
case 3:
|
|
// left
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y);
|
|
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
|
|
this.context.closePath();
|
|
break;
|
|
default:
|
|
// top
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y);
|
|
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
|
|
this.context.closePath();
|
|
}
|
|
}
|
|
|
|
draw() {
|
|
this.offset();
|
|
|
|
if (this.type === BlockType.ONE) {
|
|
this.makePath(this.hash, this.hash % 3);
|
|
this.context.fillStyle = this.primary;
|
|
this.context.fill();
|
|
|
|
this.makePath(this.hash, this.hash % 5);
|
|
this.context.fillStyle = this.accent;
|
|
this.context.fill();
|
|
} else if (this.type === BlockType.TWO) {
|
|
this.makePath(this.hash, this.hash % 4);
|
|
this.context.fillStyle = this.accent;
|
|
this.context.fill();
|
|
|
|
this.makePath(this.hash, this.hash % 3);
|
|
this.context.fillStyle = this.primary;
|
|
this.context.fill();
|
|
} else {
|
|
this.makePath(this.hash, this.hash % 7);
|
|
this.context.fillStyle = this.accent;
|
|
this.context.fill();
|
|
|
|
this.makePath(this.hash, this.hash % 8);
|
|
this.context.fillStyle = this.primary;
|
|
this.context.fill();
|
|
}
|
|
|
|
this.resetOffset();
|
|
}
|
|
}
|
|
|
|
class Position {
|
|
x: number;
|
|
y: number;
|
|
constructor(x: number, y: number) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
}
|
|
|
|
class Shape {
|
|
canvas: HTMLCanvasElement;
|
|
context: CanvasRenderingContext2D;
|
|
hash: number;
|
|
primary: string;
|
|
accent: string;
|
|
pos: Position;
|
|
scale: number;
|
|
cellSize: number;
|
|
strokeColor: string;
|
|
|
|
constructor(
|
|
c: HTMLCanvasElement,
|
|
ctx: CanvasRenderingContext2D,
|
|
hash: number,
|
|
primary: string,
|
|
accent: string,
|
|
pos: Position,
|
|
scale: number,
|
|
cellSize: number,
|
|
strokeColor: string
|
|
) {
|
|
this.canvas = c;
|
|
this.context = ctx;
|
|
this.hash = hash;
|
|
this.primary = primary;
|
|
this.accent = accent;
|
|
this.pos = pos;
|
|
this.scale = scale;
|
|
this.cellSize = cellSize;
|
|
this.strokeColor = strokeColor;
|
|
}
|
|
|
|
getColor() {
|
|
return [this.primary, this.accent][Math.abs(this.hash % 2)];
|
|
}
|
|
|
|
makePath() {
|
|
const mod = Math.abs(this.hash + 1) % 4;
|
|
|
|
switch(mod) {
|
|
case 0:
|
|
// square
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y);
|
|
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y - (this.cellSize / 2));
|
|
this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize / 2));
|
|
this.context.closePath();
|
|
break;
|
|
case 1:
|
|
//circle
|
|
this.context.beginPath();
|
|
this.context.arc(
|
|
this.pos.x + (this.cellSize / Math.PI) - 5,
|
|
this.pos.y - (this.cellSize / Math.PI) + 5,
|
|
this.cellSize / 3,
|
|
0,
|
|
Math.PI * 2,
|
|
true
|
|
);
|
|
break;
|
|
case 2:
|
|
// triangle
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x + (this.cellSize * 0.65), this.pos.y);
|
|
this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize * 0.65));
|
|
this.context.closePath();
|
|
break;
|
|
case 3:
|
|
// oval
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x - (this.cellSize * 0.2), this.pos.y + (this.cellSize * 0.2));
|
|
this.context.quadraticCurveTo(this.pos.x + (this.cellSize * 0.4), this.pos.y, this.pos.x + (this.cellSize * 0.5), this.pos.y - (this.cellSize * 0.5));
|
|
this.context.moveTo(this.pos.x + (this.cellSize * 0.5), this.pos.y - (this.cellSize * 0.5));
|
|
this.context.quadraticCurveTo(this.pos.x , this.pos.y - (this.cellSize * 0.4), this.pos.x - (this.cellSize * 0.2), this.pos.y + (this.cellSize * 0.2));
|
|
break;
|
|
default:
|
|
//square
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.pos.x, this.pos.y);
|
|
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y);
|
|
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y - (this.cellSize / 2));
|
|
this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize / 2));
|
|
this.context.closePath();
|
|
}
|
|
}
|
|
|
|
draw(hasStroke: boolean, strokeWeight: number) {
|
|
const color = this.getColor();
|
|
this.context.globalCompositeOperation = "source-over";
|
|
|
|
this.makePath();
|
|
this.context.fillStyle = color;
|
|
this.context.strokeStyle = this.strokeColor;
|
|
this.context.lineWidth = this.scale * ((4/5 * strokeWeight) / this.canvas.width);
|
|
this.context.lineJoin = "round";
|
|
this.context.lineCap = "round";
|
|
this.context.fill();
|
|
|
|
if (hasStroke) {
|
|
this.context.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @name IdentiHeart
|
|
* @author Schlipak
|
|
* @copyright Apache license 2015 Guillaume de Matos
|
|
*/
|
|
export class Identiheart {
|
|
canvas: HTMLCanvasElement
|
|
context: CanvasRenderingContext2D
|
|
margin: number
|
|
crusher: Crusher
|
|
palette = [
|
|
'#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3',
|
|
'#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39',
|
|
'#FFEB3B', '#FFC107', '#FF9800', '#FF5722', '#795548', '#607D8B'
|
|
];
|
|
|
|
primary: string = "#F44336";
|
|
accent: string = "#E91E63";
|
|
scale: number;
|
|
cellSize: number;
|
|
hash: number = 0;
|
|
blocks: Block[] = [];
|
|
shape?: Shape;
|
|
hasStroke: boolean = true;
|
|
strokeWeight = 500;
|
|
strokeColor = "#000000";
|
|
compositeOperation: GlobalCompositeOperation = 'multiply';
|
|
|
|
constructor(c: HTMLCanvasElement, margin?: number, scale?: number, ctx?: CanvasRenderingContext2D) {
|
|
this.canvas = c;
|
|
this.context = ctx ?? c.getContext("2d")!;
|
|
this.margin = margin || 5;
|
|
this.scale = scale || 20;
|
|
this.crusher = new Crusher();
|
|
this.cellSize = (this.canvas.width / 2) - (this.margin * this.scale);
|
|
}
|
|
|
|
setUsername(username: string) {
|
|
this.hash = this.crusher.hash(username);
|
|
return this;
|
|
}
|
|
|
|
setPalette(palette:string[]) {
|
|
if (palette.length < 2) {
|
|
throw new Error('Palette must be more than two elements long');
|
|
}
|
|
|
|
this.palette = palette;
|
|
return this;
|
|
}
|
|
|
|
setHasStroke(b: boolean) {
|
|
this.hasStroke = b;
|
|
return this;
|
|
}
|
|
|
|
setStrokeWeight(weight: number) {
|
|
this.strokeWeight = weight;
|
|
return this;
|
|
}
|
|
|
|
setStrokeColor(color: string) {
|
|
this.strokeColor = color;
|
|
return this;
|
|
}
|
|
|
|
setCompositeOperation(operation: GlobalCompositeOperation) {
|
|
this.compositeOperation = operation;
|
|
return this;
|
|
}
|
|
|
|
setCanvas(canvas: HTMLCanvasElement) {
|
|
this.canvas = canvas;
|
|
this.context = canvas.getContext('2d')!;
|
|
return this;
|
|
}
|
|
|
|
init() {
|
|
// Purge the block array
|
|
this.blocks = new Array<Block>();
|
|
// Purge the shape
|
|
this.shape = undefined;
|
|
|
|
// Generate colors
|
|
const crusher = new Crusher();
|
|
const subHash = crusher.hash(this.hash);
|
|
this.accent = this.palette[Math.abs(subHash % this.palette.length)];
|
|
|
|
// Clear the canvas
|
|
this.context.globalCompositeOperation = "source-over";
|
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
this.context.globalCompositeOperation = this.compositeOperation;
|
|
|
|
return this;
|
|
}
|
|
|
|
draw() {
|
|
this.init();
|
|
|
|
// Rotate the canvas -45deg
|
|
this.context.save();
|
|
this.context.translate(this.canvas.width/2, this.canvas.height/2);
|
|
this.context.rotate(- Math.PI / 4);
|
|
this.context.translate(-this.canvas.width/2, -this.canvas.height/2);
|
|
|
|
this.generateBlocks();
|
|
this.drawBlocks();
|
|
|
|
if (this.hasStroke) {
|
|
this.drawOutline();
|
|
}
|
|
|
|
this.shape = new Shape(this.canvas, this.context, this.hash, this.primary, this.accent, {
|
|
x: (this.margin * this.scale) + 1.5 * this.cellSize,
|
|
y: (this.margin * this.scale) + 0.5 * this.cellSize
|
|
}, this.scale, this.cellSize, this.strokeColor);
|
|
this.shape.draw(this.hasStroke, this.strokeWeight);
|
|
|
|
// Restore the original matrix
|
|
this.context.restore();
|
|
|
|
return this;
|
|
}
|
|
|
|
offset() {
|
|
this.context.save();
|
|
this.context.translate(0.6 * this.scale, - 0.6 * this.scale);
|
|
}
|
|
|
|
resetOffset() {
|
|
this.context.restore();
|
|
}
|
|
|
|
drawOutline() {
|
|
this.offset();
|
|
this.context.globalCompositeOperation = "source-over";
|
|
|
|
// Outer lines
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.margin * this.scale, this.margin * this.scale);
|
|
this.context.lineTo(this.margin * this.scale, this.canvas.height - (this.margin * this.scale));
|
|
this.context.lineTo(this.canvas.width - (this.margin * this.scale), this.canvas.height - (this.margin * this.scale));
|
|
this.context.lineTo(this.canvas.width - (this.margin * this.scale), this.canvas.height / 2);
|
|
this.context.lineTo(this.canvas.width / 2, this.canvas.height / 2);
|
|
this.context.lineTo(this.canvas.width / 2, this.margin * this.scale);
|
|
this.context.closePath();
|
|
|
|
this.context.strokeStyle = this.strokeColor;
|
|
this.context.lineWidth = this.scale * (this.strokeWeight / this.canvas.width);
|
|
this.context.lineJoin = "round";
|
|
this.context.lineCap = "round";
|
|
this.context.stroke();
|
|
|
|
// Inner lines
|
|
this.context.beginPath();
|
|
this.context.moveTo(this.canvas.width / 2, this.canvas.height / 2);
|
|
this.context.lineTo(this.margin * this.scale, this.canvas.height / 2);
|
|
this.context.moveTo(this.canvas.width / 2, this.canvas.height / 2);
|
|
this.context.lineTo(this.canvas.width / 2, this.canvas.height - (this.margin * this.scale));
|
|
|
|
this.context.stroke();
|
|
|
|
this.resetOffset();
|
|
this.context.globalCompositeOperation = this.compositeOperation;
|
|
}
|
|
|
|
generateBlocks() {
|
|
/*
|
|
c: HTMLCanvasElement,
|
|
ctx: CanvasRenderingContext2D,
|
|
type: BlockType,
|
|
primary: string,
|
|
accent: string,
|
|
hash: number,
|
|
cellSize: number,
|
|
margin: number,
|
|
scale: number,
|
|
pos: Position,
|
|
* */
|
|
let b1 = new Block(
|
|
this.canvas,
|
|
this.context,
|
|
BlockType.ONE,
|
|
this.primary,
|
|
this.accent,
|
|
this.hash,
|
|
this.cellSize,
|
|
this.margin,
|
|
this.scale,
|
|
new Position(this.margin * this.scale,this.margin * this.scale));
|
|
this.blocks.push(b1);
|
|
|
|
let b2 = new Block(
|
|
this.canvas,
|
|
this.context,
|
|
BlockType.TWO,
|
|
this.primary, this.accent,
|
|
this.hash,
|
|
this.cellSize,
|
|
this.margin,
|
|
this.scale,
|
|
new Position(this.margin * this.scale,this.canvas.height / 2)
|
|
);
|
|
this.blocks.push(b2);
|
|
|
|
let b3 = new Block(
|
|
this.canvas,
|
|
this.context,
|
|
BlockType.THREE,
|
|
this.primary,
|
|
this.accent,
|
|
this.hash,
|
|
this.cellSize,
|
|
this.margin,
|
|
this.scale,
|
|
new Position(this.canvas.width / 2, this.canvas.height / 2)
|
|
);
|
|
this.blocks.push(b3);
|
|
}
|
|
|
|
drawBlocks() {
|
|
if (this.blocks.length == 0) {
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < this.blocks.length; i++) {
|
|
this.blocks[i].draw();
|
|
}
|
|
}
|
|
}
|