calvesca's picture
Upload folder using huggingface_hub
0bd62e5 verified
import {
Container,
type IRenderer,
type DisplayObject,
RenderTexture,
Texture,
Sprite,
Filter
} from "pixi.js";
/**
* GLSL Shader that takes two textures and erases the second texture from the first.
*/
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);
}`;
/**
* Interface holding data for a layer
*/
export interface LayerScene {
/**
* The texture used for tracking brush strokes.
*/
draw_texture: RenderTexture;
/**
* The texture used for tracking eraser strokes.
*/
erase_texture: RenderTexture;
/**
* The sprite used for displaying the composite of the draw and erase textures.
*/
composite: Sprite;
/**
* The filter used for combining the draw and erase textures into a composite texture.
*/
filter?: Filter;
}
/**
* Interface for managing layers.
*/
interface LayerManager {
/**
* Adds a layer to the container.
* @param layer The container to add the layer to.
* @param renderer The renderer to use for the layer.
* @param width the width of the layer
* @param height the height of the layer
*/
add_layer(
container: Container,
renderer: IRenderer,
width: number,
height: number
): [LayerScene, LayerScene[]];
/**
* Swaps the layer with the layer above or below it.
* @param layer The index layer to swap.
* @param direction The direction to swap the layer.
*/
swap_layers(layer: number, direction: "up" | "down"): LayerScene;
/**
* Changes the active layer.
* @param layer The index of the layer to make active.
*/
change_active_layer(layer: number): LayerScene;
/**
* Resizes the layers.
* @param width The new width of the layers.
* @param height The new height of the layers.
*/
reset(): void;
/**
* Gets the layers.
* @returns The layers.
*/
get_layers(): LayerScene[];
add_layer_from_blob(
container: Container,
renderer: IRenderer,
blob: Blob,
view: HTMLCanvasElement
): Promise<[LayerScene, LayerScene[]]>;
}
/**
* Swaps two adjacent elements in an array.
* @param array The array to swap elements in.
* @param index The index of the first element to swap.
*/
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]];
}
/**
* Creates a layer manager.
* @param canvas_resize a function to resize the canvas
* @returns a layer manager
*/
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];
}