|
<script lang="ts"> |
|
import { getContext, onDestroy, onMount, tick } from "svelte"; |
|
|
|
import { type ToolContext, TOOL_KEY } from "./Tools.svelte"; |
|
import { type EditorContext, EDITOR_KEY } from "../ImageEditor.svelte"; |
|
import { crop_canvas, resize_and_reposition, type CropCommand } from "./crop"; |
|
|
|
import Cropper from "./Cropper.svelte"; |
|
|
|
const { active_tool, register_tool } = getContext<ToolContext>(TOOL_KEY); |
|
const { |
|
dimensions, |
|
editor_box, |
|
pixi, |
|
crop, |
|
command_manager, |
|
current_history |
|
} = getContext<EditorContext>(EDITOR_KEY); |
|
|
|
let cropper: CropCommand | null; |
|
|
|
export let crop_constraint: [number, number] | `${string}:${string}` | null; |
|
|
|
let _crop_constraint: number | null = null; |
|
$: { |
|
if (typeof crop_constraint === "string") { |
|
const [w, h] = crop_constraint.split(":"); |
|
_crop_constraint = parseInt(w) / parseInt(h); |
|
} else if (Array.isArray(crop_constraint)) { |
|
_crop_constraint = crop_constraint[0] / crop_constraint[1]; |
|
} |
|
} |
|
|
|
let w_p = 1; |
|
let h_p = 1; |
|
let l_p = 0; |
|
let t_p = 0; |
|
|
|
let current_opacity = 0; |
|
|
|
let manually_cropped = false; |
|
|
|
$: { |
|
if (!manually_cropped && _crop_constraint && $pixi && $active_tool) { |
|
requestAnimationFrame(() => { |
|
initial_crop(); |
|
}); |
|
} |
|
} |
|
|
|
let c: CropCommand | null = null; |
|
|
|
async function initial_crop(): Promise<void> { |
|
if (c || $current_history.previous) return; |
|
const { new_height, new_width, x_offset, y_offset } = resize_and_reposition( |
|
$editor_box.child_width, |
|
$editor_box.child_height, |
|
"c", |
|
_crop_constraint!, |
|
|
|
$editor_box.child_width, |
|
$editor_box.child_height |
|
); |
|
|
|
w_p = new_width / $editor_box.child_width; |
|
h_p = new_height / $editor_box.child_height; |
|
l_p = x_offset / $editor_box.child_width; |
|
t_p = y_offset / $editor_box.child_height; |
|
|
|
c = crop_canvas($pixi!.renderer, $pixi!.mask_container, crop, 0.2); |
|
c.start(...$dimensions, current_crop, false); |
|
|
|
c.continue( |
|
[ |
|
l_p * $dimensions[0], |
|
t_p * $dimensions[1], |
|
w_p * $dimensions[0], |
|
h_p * $dimensions[1] |
|
], |
|
false |
|
); |
|
c.stop(); |
|
|
|
c.execute(); |
|
c = null; |
|
current_opacity = 0; |
|
} |
|
|
|
function handle_crop( |
|
type: "start" | "stop" | "continue", |
|
|
|
{ |
|
x, |
|
y, |
|
width, |
|
height |
|
}: { |
|
x: number; |
|
y: number; |
|
width: number; |
|
height: number; |
|
} |
|
): void { |
|
if (!$pixi) return; |
|
|
|
if (type === "start") { |
|
if (cropper) { |
|
current_opacity = cropper.stop(); |
|
cropper = null; |
|
} |
|
|
|
cropper = crop_canvas( |
|
$pixi?.renderer, |
|
$pixi.mask_container, |
|
crop, |
|
current_opacity |
|
); |
|
cropper.start(...$dimensions, current_crop); |
|
} else if (type === "continue") { |
|
if (!cropper) return; |
|
cropper.continue([ |
|
x * $dimensions[0], |
|
y * $dimensions[1], |
|
width * $dimensions[0], |
|
height * $dimensions[1] |
|
]); |
|
} else if (type === "stop") { |
|
if (!cropper) return; |
|
command_manager.execute(cropper); |
|
} |
|
manually_cropped = true; |
|
} |
|
|
|
let measured = false; |
|
|
|
function get_measurements(): void { |
|
if (measured) return; |
|
if (!$editor_box.child_height) return; |
|
const _height = $editor_box.child_height; |
|
const _width = $editor_box.child_width; |
|
const _top = $editor_box.child_top - $editor_box.parent_top; |
|
const _left = $editor_box.child_left - $editor_box.parent_left; |
|
|
|
w_p = _width / $editor_box.child_width; |
|
h_p = _height / $editor_box.child_height; |
|
l_p = |
|
(_left - $editor_box.child_left + $editor_box.parent_left) / |
|
$editor_box.child_width; |
|
t_p = |
|
(_top - $editor_box.child_top + $editor_box.parent_top) / |
|
$editor_box.child_height; |
|
|
|
measured = true; |
|
} |
|
|
|
let current_crop: [number, number, number, number]; |
|
$: current_crop = [ |
|
l_p * $dimensions[0], |
|
t_p * $dimensions[1], |
|
w_p * $dimensions[0], |
|
h_p * $dimensions[1] |
|
]; |
|
|
|
$: { |
|
l_p = $crop[0]; |
|
t_p = $crop[1]; |
|
w_p = $crop[2]; |
|
h_p = $crop[3]; |
|
} |
|
|
|
$: $editor_box && get_measurements(); |
|
|
|
const unregister = register_tool("crop"); |
|
onDestroy(unregister); |
|
</script> |
|
|
|
{#if $active_tool === "crop" && measured} |
|
<Cropper |
|
crop_constraint={_crop_constraint} |
|
{w_p} |
|
{h_p} |
|
{l_p} |
|
{t_p} |
|
{editor_box} |
|
on:crop_start={({ detail }) => handle_crop("start", detail)} |
|
on:crop_continue={({ detail }) => handle_crop("continue", detail)} |
|
on:crop_end={({ detail }) => handle_crop("stop", detail)} |
|
/> |
|
{/if} |
|
|