svelte media crop svelte-crop-window

A crop window with touch and mouse gestures to zoom, pan and rotate an image or video.

Media snaps back to cover the crop area.

Looking for contributions, code review and feedback! Feel free to open an issue on the GitHub repository or connect with me via the Svelte discord (sabine#8815).

This is still in an experimental stage. Changes to the API are to be expected. After v1.0.0, this package will strictly follow semantic versioning.

The code of this demo page is here.

Example with controls

Result

result

Aspect ratio

Scale / Rotation / Position

scale
0
rotation
0
x
y

Colors

overlay color
line color
background color

Shape

Minimal code example

Note: You must wrap the component with an element that has a determined height (and width), as the component will always take up 100% of the available width and height.
// in script tag
import { CropWindow, defaultValue } from 'svelte-crop-window';

let media: Media = {
    content_type: 'image',
    url: '/svelte-crop-window/hintersee-3601004.jpg'
};

let value = { ...defaultValue };

// in the template
<div style="height:20em; background: #222">
    <CropWindow bind:value {media} />
</div>

Props and defaults

See here.
<div style="height:20em">
    <CropWindow
        bind:value
        {media}
        {options}
    />
</div>
const defaultOverlayOptions: OverlayOptions = {
    overlay_color: '#222222',
    line_color: '#FFFFFF',
    show_third_lines: true,
};

const defaultOptions: Options<OverlayOptions> = {
    shape: 'rect',
    crop_window_margin: 10,

    overlay: Overlay,
    overlay_options: defaultOverlayOptions,
}

const defaultValue: CropValue = {
    position: { x: 0, y: 0 },
    aspect: 1.0,
    rotation: 0,
    scale: 0
};

How to Crop

result

Display in HTML without actually cropping:

<div style={`position:relative; overflow:hidden;
        height:{HEIGHT}px; width:{value.aspect * HEIGHT}px;
        border-radius: { options.shape == 'round' ? '50%' : '0' }`}>
    <video
        style={`transform: translateX(-50%) translateY(-50%) rotate({value.rotation || 0}deg);
    height: {(value.scale || 0.0) * HEIGHT}px;
    margin-left: {HEIGHT * value.aspect / 2 + (value.position.x || 0) * HEIGHT}px;
    margin-top: {HEIGHT / 2 + (value.position.y || 0) * HEIGHT}px;
    max-width:none`}
        src={url}
    />
</div>

Pseudo code to crop

  1. Choose a target_height and calculate the width for the cropped image:
    let target_width = target_height * value.aspect;
  2. Calculate factor by which to scale:
    let s = value.scale * target_height / media.height;
  3. Scale media by s:
    let resized_media = scale(media, s);
  4. Rotate media by value.rotation:
    let resized_and_rotated_media =
        rotate(resized_media, value.rotation);
  5. Calculate top left position of area to extract:
    let left =
        (resized_and_rotated_media.width - target_width) / 2.0
        - value.x * target_height;
    let top =
        (resized_and_rotated_media.height - target_height) / 2.0
        - value.y * target_height;
  6. Extract area:
    let cropped_media =
        extract_area(resized_and_rotated_media, left, top,
                     target_width, target_height);

What this component doesn't do

  1. Does not modify/crop the image, you have to do that by whatever means make sense for your application.
  2. Doesn't (yet) provide usable controls. Currently, you need to implement your own.

    Similar to the overlay, it would be nice to include some controls to make this more usable out of the box. Contributions are very welcome.

Inspiration and Acknowledgements

One big inspiration for this component was the Android library uCrop by Yalantis. What is particularly valuable is that the developers shared their thought process in this blog post.

Another very helpful resource was svelte-easy-crop which gave me a basic understanding of how to implement a crop window component in Svelte (and HTML/JS in general).

There's no code being reused between either of these components and this one (all calculations had to be recreated from textbook math).

Video from shantararam at pixabay: mountain-nature-snow-old-mountain-8837