|
import {
|
|
Graphics,
|
|
type IRenderer,
|
|
type Container,
|
|
Sprite,
|
|
RenderTexture
|
|
} from "pixi.js";
|
|
import { type Command } from "../utils/commands";
|
|
import { spring } from "svelte/motion";
|
|
import type { Writable } from "svelte/store";
|
|
|
|
export interface CropCommand extends Command {
|
|
start: (
|
|
width: number,
|
|
height: number,
|
|
previous_crop: [number, number, number, number],
|
|
preview?: boolean,
|
|
set_previous?: boolean
|
|
) => void;
|
|
stop: () => number;
|
|
continue: (
|
|
crop_size: [number, number, number, number],
|
|
preview?: boolean
|
|
) => void;
|
|
}
|
|
|
|
export function crop_canvas(
|
|
renderer: IRenderer,
|
|
mask_container: Container,
|
|
crop: Writable<[number, number, number, number]>,
|
|
current_opacity = 0
|
|
): CropCommand {
|
|
let text: RenderTexture;
|
|
let sprite: Sprite;
|
|
const mask_graphics = new Graphics();
|
|
let previous_crop: [number, number, number, number];
|
|
let final_crop: [number, number, number, number];
|
|
let width: number;
|
|
let height: number;
|
|
let alpha_spring = spring(current_opacity, {
|
|
stiffness: 0.1,
|
|
damping: 0.5
|
|
});
|
|
|
|
let spring_value = current_opacity;
|
|
alpha_spring.subscribe((value) => {
|
|
if (!final_crop) return;
|
|
spring_value = value;
|
|
crop_mask(width, height, final_crop, true);
|
|
});
|
|
|
|
function crop_mask(
|
|
width: number,
|
|
height: number,
|
|
crop_size: [number, number, number, number],
|
|
preview: boolean
|
|
): void {
|
|
mask_graphics.clear();
|
|
if (preview) {
|
|
mask_graphics.beginFill(0xffffff, spring_value);
|
|
mask_graphics.drawRect(0, 0, width, height);
|
|
mask_graphics.endFill();
|
|
}
|
|
mask_graphics.beginFill(0xffffff, 1);
|
|
mask_graphics.drawRect(...crop_size);
|
|
mask_graphics.endFill();
|
|
|
|
renderer.render(mask_graphics, {
|
|
renderTexture: text
|
|
});
|
|
}
|
|
|
|
let clean = true;
|
|
let stopped = false;
|
|
|
|
return {
|
|
start: (
|
|
_width: number,
|
|
_height: number,
|
|
_previous_crop: [number, number, number, number],
|
|
_preview = true,
|
|
set_previous = true
|
|
) => {
|
|
clean = false;
|
|
text = RenderTexture.create({
|
|
width: _width,
|
|
height: _height
|
|
});
|
|
|
|
crop_mask(_width, _height, _previous_crop, _preview);
|
|
sprite = new Sprite(text);
|
|
mask_container.mask = sprite;
|
|
width = _width;
|
|
height = _height;
|
|
if (set_previous)
|
|
previous_crop = JSON.parse(JSON.stringify(_previous_crop));
|
|
},
|
|
continue: (crop_size: [number, number, number, number], preview = true) => {
|
|
final_crop = JSON.parse(JSON.stringify(crop_size));
|
|
if (spring_value === 0.2) {
|
|
crop_mask(width, height, final_crop, preview);
|
|
} else {
|
|
alpha_spring.set(0.2);
|
|
}
|
|
},
|
|
|
|
undo() {
|
|
this.start(width, height, previous_crop, false);
|
|
crop.set([
|
|
previous_crop[0] / width,
|
|
previous_crop[1] / height,
|
|
previous_crop[2] / width,
|
|
previous_crop[3] / height
|
|
]);
|
|
clean = true;
|
|
},
|
|
stop() {
|
|
stopped = true;
|
|
return spring_value;
|
|
},
|
|
execute() {
|
|
if (clean) {
|
|
this.start(width, height, final_crop, false);
|
|
|
|
crop.set([
|
|
final_crop[0] / width,
|
|
final_crop[1] / height,
|
|
final_crop[2] / width,
|
|
final_crop[3] / height
|
|
]);
|
|
clean = true;
|
|
} else {
|
|
if (!stopped) {
|
|
alpha_spring.set(0);
|
|
}
|
|
|
|
crop.set([
|
|
final_crop[0] / width,
|
|
final_crop[1] / height,
|
|
final_crop[2] / width,
|
|
final_crop[3] / height
|
|
]);
|
|
|
|
clean = true;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export function resize_and_reposition(
|
|
original_width: number,
|
|
original_height: number,
|
|
anchor: "t" | "r" | "l" | "b" | "tl" | "tr" | "bl" | "br" | "c",
|
|
aspect_ratio: number,
|
|
max_width: number,
|
|
max_height: number
|
|
): {
|
|
new_width: number;
|
|
new_height: number;
|
|
x_offset: number;
|
|
y_offset: number;
|
|
} {
|
|
let new_width = original_width;
|
|
let new_height = original_height;
|
|
|
|
|
|
if (anchor.includes("t") || anchor.includes("b") || anchor == "c") {
|
|
new_width = original_height * aspect_ratio;
|
|
}
|
|
if (anchor.includes("l") || anchor.includes("r") || anchor == "c") {
|
|
new_height = original_width / aspect_ratio;
|
|
}
|
|
|
|
|
|
new_height = new_height || new_width / aspect_ratio;
|
|
new_width = new_width || new_height * aspect_ratio;
|
|
|
|
|
|
if (new_width > max_width) {
|
|
new_width = max_width;
|
|
new_height = new_width / aspect_ratio;
|
|
}
|
|
if (new_height > max_height) {
|
|
new_height = max_height;
|
|
new_width = new_height * aspect_ratio;
|
|
}
|
|
|
|
|
|
let x_offset = 0;
|
|
let y_offset = 0;
|
|
if (anchor.includes("r")) {
|
|
x_offset = original_width - new_width;
|
|
} else if (anchor.includes("l")) {
|
|
x_offset = 0;
|
|
} else {
|
|
|
|
x_offset = (original_width - new_width) / 2;
|
|
}
|
|
|
|
if (anchor.includes("b")) {
|
|
y_offset = original_height - new_height;
|
|
} else if (anchor.includes("t")) {
|
|
y_offset = 0;
|
|
} else {
|
|
|
|
y_offset = (original_height - new_height) / 2;
|
|
}
|
|
|
|
return {
|
|
new_width: new_width,
|
|
new_height: new_height,
|
|
x_offset: x_offset,
|
|
y_offset: y_offset
|
|
};
|
|
}
|
|
|