|
import {
|
|
Container,
|
|
type IRenderer,
|
|
type DisplayObject,
|
|
RenderTexture,
|
|
Texture,
|
|
Sprite,
|
|
Filter
|
|
} from "pixi.js";
|
|
|
|
|
|
|
|
|
|
export const erase_shader = `
|
|
precision highp float;
|
|
|
|
uniform sampler2D uDrawingTexture;
|
|
uniform sampler2D uEraserTexture;
|
|
|
|
varying vec2 vTextureCoord;
|
|
|
|
void main(void) {
|
|
vec4 drawingColor = texture2D(uDrawingTexture,vTextureCoord);
|
|
vec4 eraserColor = texture2D(uEraserTexture, vTextureCoord);
|
|
|
|
// Use the alpha of the eraser to determine how much to "erase" from the drawing
|
|
float alpha = 1.0 - eraserColor.a;
|
|
gl_FragColor = vec4(drawingColor.rgb * alpha, drawingColor.a * alpha);
|
|
}`;
|
|
|
|
|
|
|
|
|
|
export interface LayerScene {
|
|
|
|
|
|
|
|
draw_texture: RenderTexture;
|
|
|
|
|
|
|
|
erase_texture: RenderTexture;
|
|
|
|
|
|
|
|
composite: Sprite;
|
|
|
|
|
|
|
|
filter?: Filter;
|
|
}
|
|
|
|
|
|
|
|
|
|
interface LayerManager {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
add_layer(
|
|
container: Container,
|
|
renderer: IRenderer,
|
|
width: number,
|
|
height: number
|
|
): [LayerScene, LayerScene[]];
|
|
|
|
|
|
|
|
|
|
|
|
swap_layers(layer: number, direction: "up" | "down"): LayerScene;
|
|
|
|
|
|
|
|
|
|
change_active_layer(layer: number): LayerScene;
|
|
|
|
|
|
|
|
|
|
|
|
reset(): void;
|
|
|
|
|
|
|
|
|
|
get_layers(): LayerScene[];
|
|
|
|
add_layer_from_blob(
|
|
container: Container,
|
|
renderer: IRenderer,
|
|
blob: Blob,
|
|
view: HTMLCanvasElement
|
|
): Promise<[LayerScene, LayerScene[]]>;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function swap_adjacent(array: any[], index: number): void {
|
|
if (index < 0 || index >= array.length - 1) {
|
|
throw new Error("Index out of bounds");
|
|
}
|
|
|
|
[array[index], array[index + 1]] = [array[index + 1], array[index]];
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function layer_manager(): LayerManager {
|
|
let _layers: LayerScene[] = [];
|
|
let current_layer = 0;
|
|
let position = 0;
|
|
|
|
return {
|
|
add_layer: function (
|
|
container: Container,
|
|
renderer: IRenderer,
|
|
width: number,
|
|
height: number
|
|
): [LayerScene, LayerScene[]] {
|
|
const layer_container = new Container() as Container & DisplayObject;
|
|
position++;
|
|
layer_container.zIndex = position;
|
|
|
|
const composite_texture = RenderTexture.create({
|
|
width,
|
|
height
|
|
});
|
|
|
|
const composite = new Sprite(composite_texture) as Sprite & DisplayObject;
|
|
|
|
layer_container.addChild(composite);
|
|
|
|
composite.zIndex = position;
|
|
|
|
const layer_scene: LayerScene = {
|
|
draw_texture: RenderTexture.create({
|
|
width,
|
|
height
|
|
}),
|
|
erase_texture: RenderTexture.create({
|
|
width,
|
|
height
|
|
}),
|
|
composite
|
|
};
|
|
|
|
const erase_filter = new Filter(undefined, erase_shader, {
|
|
uEraserTexture: layer_scene.erase_texture,
|
|
uDrawingTexture: layer_scene.draw_texture
|
|
});
|
|
|
|
composite.filters = [erase_filter];
|
|
|
|
container.addChild(layer_container);
|
|
|
|
_layers.push(layer_scene);
|
|
|
|
return [layer_scene, _layers];
|
|
},
|
|
|
|
swap_layers: function (
|
|
layer: number,
|
|
direction: "up" | "down"
|
|
): LayerScene {
|
|
if (direction === "up") {
|
|
swap_adjacent(_layers, layer);
|
|
} else {
|
|
swap_adjacent(_layers, layer - 1);
|
|
}
|
|
return _layers[layer];
|
|
},
|
|
|
|
change_active_layer: function (layer: number): LayerScene {
|
|
current_layer = layer;
|
|
return _layers[layer];
|
|
},
|
|
reset() {
|
|
_layers.forEach((layer) => {
|
|
layer.draw_texture.destroy(true);
|
|
layer.erase_texture.destroy(true);
|
|
layer.composite.destroy(true);
|
|
});
|
|
_layers = [];
|
|
current_layer = 0;
|
|
position = 0;
|
|
},
|
|
async add_layer_from_blob(
|
|
container: Container,
|
|
renderer: IRenderer,
|
|
blob: Blob,
|
|
view: HTMLCanvasElement
|
|
) {
|
|
const img = await createImageBitmap(blob);
|
|
const bitmap_texture = Texture.from(img);
|
|
|
|
const [w, h] = resize_to_fit(
|
|
bitmap_texture.width,
|
|
bitmap_texture.height,
|
|
view.width,
|
|
view.height
|
|
);
|
|
|
|
const sprite = new Sprite(bitmap_texture) as Sprite & DisplayObject;
|
|
sprite.zIndex = 0;
|
|
|
|
sprite.width = w;
|
|
sprite.height = h;
|
|
|
|
const [layer, layers] = this.add_layer(
|
|
container,
|
|
renderer,
|
|
view.width,
|
|
view.height
|
|
);
|
|
|
|
renderer.render(sprite, {
|
|
renderTexture: layer.draw_texture
|
|
});
|
|
|
|
return [layer, layers];
|
|
},
|
|
get_layers() {
|
|
return _layers;
|
|
}
|
|
};
|
|
}
|
|
|
|
function resize_to_fit(
|
|
inner_width: number,
|
|
inner_height: number,
|
|
outer_width: number,
|
|
outer_height: number
|
|
): [number, number] {
|
|
if (inner_width <= outer_width && inner_height <= outer_height) {
|
|
return [inner_width, inner_height];
|
|
}
|
|
|
|
const inner_aspect = inner_width / inner_height;
|
|
const outer_aspect = outer_width / outer_height;
|
|
|
|
let new_width, new_height;
|
|
|
|
if (inner_aspect > outer_aspect) {
|
|
new_width = outer_width;
|
|
new_height = outer_width / inner_aspect;
|
|
} else {
|
|
new_height = outer_height;
|
|
new_width = outer_height * inner_aspect;
|
|
}
|
|
|
|
return [new_width, new_height];
|
|
}
|
|
|