|
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]; |
|
} |
|
|