Parcourir la source

screenshot: autocrop

David Sehnal il y a 4 ans
Parent
commit
316076d81e

+ 2 - 1
src/mol-plugin-ui/controls/common.tsx

@@ -308,6 +308,7 @@ export function IconButton(props: {
 
 export type ToggleButtonProps = {
     style?: React.CSSProperties,
+    inline?: boolean,
     className?: string,
     disabled?: boolean,
     label?: string | JSX.Element,
@@ -327,7 +328,7 @@ export class ToggleButton extends React.PureComponent<ToggleButtonProps> {
         const props = this.props;
         const label = props.label;
         const className = props.isSelected ? `${props.className || ''} msp-control-current` : props.className;
-        return <Button icon={this.props.icon} onClick={this.onClick} title={this.props.title}
+        return <Button icon={this.props.icon} onClick={this.onClick} title={this.props.title} inline={this.props.inline}
             disabled={props.disabled} style={props.style} className={className}>
             {label && this.props.isSelected ? <b>{label}</b> : label}
         </Button>;

+ 6 - 0
src/mol-plugin-ui/controls/icons.tsx

@@ -99,6 +99,12 @@ const _Code = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M9.4
 export function CodeSvg() { return _Code; }
 const _Copy = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z' /></svg>;
 export function CopySvg() { return _Copy; }
+const _Crop = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M17 15h2V7c0-1.1-.9-2-2-2H9v2h8v8zM7 17V1H5v4H1v2h4v10c0 1.1.9 2 2 2h10v4h2v-4h4v-2H7z' /></svg>;
+export function CropSvg() { return _Crop; }
+const _CropFree = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M3 5v4h2V5h4V3H5c-1.1 0-2 .9-2 2zm2 10H3v4c0 1.1.9 2 2 2h4v-2H5v-4zm14 4h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zm0-16h-4v2h4v4h2V5c0-1.1-.9-2-2-2z' /></svg>;
+export function CropFreeSvg() { return _CropFree; }
+const _CropOriginal = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5.04-6.71l-2.75 3.54-1.96-2.36L6.5 17h11l-3.54-4.71z' /></svg>;
+export function CropOrginalSvg() { return _CropOriginal; }
 const _DeleteOutlined = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z' /></svg>;
 export function DeleteOutlinedSvg() { return _DeleteOutlined; }
 const _Delete = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z' /></svg>;

+ 0 - 8
src/mol-plugin-ui/skin/base/components/controls.scss

@@ -399,14 +399,6 @@
         display: 'block';
     }
 
-    canvas.msp-transparent-screenshot {
-        background-color: $default-background;
-        background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey),
-        linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey);
-        background-size: 30px 30px;
-        background-position: 0 0, 15px 15px;
-    }
-
     > span {
         margin-top: 6px;
         display: block;

+ 80 - 50
src/mol-plugin-ui/viewport/screenshot.tsx

@@ -6,21 +6,21 @@
  */
 
 import * as React from 'react';
-import { ParameterControls } from '../controls/parameters';
-import { PluginUIComponent } from '../base';
-import { debounceTime } from 'rxjs/operators';
+import { useRef, useState } from 'react';
 import { Subject } from 'rxjs';
-import { ViewportScreenshotHelper, ViewportScreenshotHelperParams } from '../../mol-plugin/util/viewport-screenshot';
-import { Button, ExpandGroup } from '../controls/common';
+import { debounceTime } from 'rxjs/operators';
+import { Viewport } from '../../mol-canvas3d/camera/util';
 import { CameraHelperProps } from '../../mol-canvas3d/helper/camera-helper';
+import { equalEps } from '../../mol-math/linear-algebra/3d/common';
 import { PluginCommands } from '../../mol-plugin/commands';
-import { StateExportImportControls, LocalStateSnapshotParams } from '../state/snapshots';
-import { CopySvg, GetAppSvg, RefreshSvg } from '../controls/icons';
 import { PluginContext } from '../../mol-plugin/context';
+import { ViewportScreenshotHelper, ViewportScreenshotHelperParams } from '../../mol-plugin/util/viewport-screenshot';
+import { PluginUIComponent } from '../base';
+import { Button, ExpandGroup, ToggleButton } from '../controls/common';
+import { CopySvg, CropFreeSvg, CropOrginalSvg, CropSvg, GetAppSvg } from '../controls/icons';
+import { ParameterControls } from '../controls/parameters';
 import { useBehavior } from '../hooks/use-behavior';
-import { Viewport } from '../../mol-canvas3d/camera/util';
-import { useEffect, useRef, useState } from 'react';
-import { equalEps } from '../../mol-math/linear-algebra/3d/common';
+import { LocalStateSnapshotParams, StateExportImportControls } from '../state/snapshots';
 
 interface ImageControlsState {
     showPreview: boolean
@@ -55,9 +55,27 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
 
         ctx.clearRect(0, 0, w, h);
         const frame = getViewportFrame(width, height, w, h);
+        if (this.plugin.helpers.viewportScreenshot?.values.transparent) {
+            this.drawCheckerboard(ctx, frame);
+        }
         ctx.drawImage(canvas, frame.x, frame.y, frame.width, frame.height);
     }
 
+    private drawCheckerboard(ctx: CanvasRenderingContext2D, frame: Viewport) {
+        // must be odd number!
+        const s = 13;
+        for (let i = 0; i < frame.width; i += s) {
+            for (let j = 0; j < frame.height; j += s) {
+                ctx.fillStyle = (i + j) % 2 ? '#ffffff' : '#bfbfbf';
+
+                const x = frame.x + i, y = frame.y + j;
+                const w = i + s > frame.width ? frame.width - i : s;
+                const h = j + s > frame.height ? frame.height - j : s;
+                ctx.fillRect(x, y, w, h);
+            }
+        }
+    }
+
     private download = () => {
         this.plugin.helpers.viewportScreenshot?.download();
         this.props.close();
@@ -96,10 +114,6 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
             this.updateQueue.next();
         });
 
-        this.subscribe(this.plugin.helpers.viewportScreenshot!.behaviors.relativeCrop, () => {
-            this.forceUpdate();
-        });
-
         this.preview();
     }
 
@@ -120,28 +134,24 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
         PluginCommands.State.Snapshots.OpenFile(this.plugin, { file: e.target.files![0] });
     }
 
-    onCanvasClick = (e: React.MouseEvent) => {
+    onCanvasContextMenu = (e: React.MouseEvent) => {
         e.preventDefault();
+        e.stopPropagation();
     };
 
     render() {
         const values = this.plugin.helpers.viewportScreenshot!.values;
-        const crop = this.plugin.helpers.viewportScreenshot!.relativeCrop;
 
         return <div>
             <div className='msp-image-preview'>
                 <div style={{ position: 'relative', width: '100%', height: '100%' }}>
-                    <canvas ref={this.canvasRef} onClick={this.onCanvasClick} onContextMenu={this.onCanvasClick} style={{ width: '100%', height: '180px' }} className={values.transparent ? 'msp-transparent-screenshot' : void 0}></canvas>
+                    <canvas ref={this.canvasRef} onClick={this.onCanvasContextMenu} onContextMenu={this.onCanvasContextMenu} style={{ width: '100%', height: '180px' }}></canvas>
                     <ViewportFrame plugin={this.plugin} canvasRef={this.canvasRef} />
                 </div>
-                <span>
-                    Drag the frame to crop the image.
-                    {!isFullFrame(crop) && <Button icon={RefreshSvg} title='Reset Crop' inline
-                        style={{ width: 'auto', background: 'transparent', height: '15px', lineHeight: '15px', padding: '0 4px', marginLeft: '8px', border: 'none' }}
-                        onClick={() => this.plugin.helpers.viewportScreenshot!.behaviors.relativeCrop.next({ x: 0, y: 0, width: 1, height: 1 })} />}
-                </span>
+                <CropControls plugin={this.plugin} />
             </div>
             <div className='msp-flex-row'>
+                {/* TODO: figure out how to do copy/paste in Firefox */}
                 {!!(navigator.clipboard as any).write && <Button icon={CopySvg} onClick={this.copy} disabled={this.state.isDisabled}>Copy</Button>}
                 <Button icon={GetAppSvg} onClick={this.download} disabled={this.state.isDisabled}>Download</Button>
             </div>
@@ -160,19 +170,33 @@ function isFullFrame(crop: Viewport) {
     return equalEps(crop.x, 0, 1e-5) && equalEps(crop.y, 0, 1e-5) && equalEps(crop.width, 1, 1e-5) && equalEps(crop.height, 1, 1e-5);
 }
 
-export function ViewportFrame({ plugin, canvasRef }: { plugin: PluginContext, canvasRef: React.RefObject<HTMLCanvasElement> }) {
+function CropControls({ plugin }: { plugin: PluginContext }) {
     const helper = plugin.helpers.viewportScreenshot;
     const params = useBehavior(helper?.behaviors.values);
     const crop = useBehavior(helper?.behaviors.relativeCrop);
-    const cropFrameRef = useRef<Viewport>({ x: 0, y: 0, width: 0, height: 0 });
 
+    if (!params || !crop || !helper) return null;
+
+    return <div style={{ width: '100%', height: '24px' }}>
+        <ToggleButton icon={CropOrginalSvg} title='Auto-crop' inline isSelected={params.autoCrop}
+            style={{ background: 'transparent', float: 'left', width: 'auto',  height: '24px', lineHeight: '24px' }}
+            toggle={() => helper.behaviors.values.next({ ...params, autoCrop: !params.autoCrop })} label={'Auto-crop ' + (params.autoCrop ? 'On' : 'Off') } />
+
+        {!params.autoCrop && <Button icon={CropSvg} title='Crop'
+            style={{ background: 'transparent', float: 'right', height: '24px', lineHeight: '24px', width: '24px', padding: '0' }}
+            onClick={() => helper.autocrop()} />}
+        {!isFullFrame(crop) && <Button icon={CropFreeSvg} title='Reset Crop'
+            style={{ background: 'transparent', float: 'right', height: '24px', lineHeight: '24px', width: '24px', padding: '0' }}
+            onClick={() => helper.resetCrop()} />}
+    </div>;
+}
+
+function ViewportFrame({ plugin, canvasRef }: { plugin: PluginContext, canvasRef: React.RefObject<HTMLCanvasElement> }) {
+    const helper = plugin.helpers.viewportScreenshot;
+    const params = useBehavior(helper?.behaviors.values);
+    const crop = useBehavior(helper?.behaviors.relativeCrop);
+    const cropFrameRef = useRef<Viewport>({ x: 0, y: 0, width: 0, height: 0 });
     useBehavior(params?.resolution.name === 'viewport' ? plugin.canvas3d?.resized : void 0);
-    useEffect(() => {
-        return () => {
-            if (onMove) document.removeEventListener('mousemove', onMove);
-            if (onEnd) document.removeEventListener('mouseup', onEnd);
-        };
-    }, []);
 
     const [drag, setDrag] = React.useState<string>('');
     const [start, setStart] = useState([0, 0]);
@@ -234,8 +258,8 @@ export function ViewportFrame({ plugin, canvasRef }: { plugin: PluginContext, ca
 
     cropFrame.x = rectCrop.l;
     cropFrame.y = rectCrop.t;
-    cropFrame.width = rectCrop.r - rectCrop.l;
-    cropFrame.height = rectCrop.b - rectCrop.t;
+    cropFrame.width = rectCrop.r - rectCrop.l + 1;
+    cropFrame.height = rectCrop.b - rectCrop.t + 1;
 
     cropFrameRef.current = cropFrame;
 
@@ -250,15 +274,18 @@ export function ViewportFrame({ plugin, canvasRef }: { plugin: PluginContext, ca
         const p = [e.pageX, e.pageY];
         setStart(p);
         setCurrent(p);
-        document.addEventListener('mouseup', onEnd);
-        document.addEventListener('mousemove', onMove);
+        window.addEventListener('mouseup', onEnd);
+        window.addEventListener('mousemove', onMove);
     };
 
     const onEnd = () => {
-        document.removeEventListener('mouseup', onEnd);
-        document.removeEventListener('mousemove', onMove);
+        window.removeEventListener('mouseup', onEnd);
+        window.removeEventListener('mousemove', onMove);
 
         const cropFrame = cropFrameRef.current;
+        if (params?.autoCrop) {
+            helper.behaviors.values.next({ ...params, autoCrop: false });
+        }
         helper?.behaviors.relativeCrop.next({
             x: (cropFrame.x - frame.x) / frame.width,
             y: (cropFrame.y - frame.y) / frame.height,
@@ -271,29 +298,32 @@ export function ViewportFrame({ plugin, canvasRef }: { plugin: PluginContext, ca
         setCurrent(p);
     };
 
+    const contextMenu = (e: React.MouseEvent) => {
+        e.preventDefault();
+        e.stopPropagation();
+    };
+
     const d = 4;
-    const border = `6px solid rgba(255, 87, 45, 0.75)`;
+    const border = `3px solid rgba(255, 87, 45, 0.75)`;
     const transparent = 'transparent';
 
     return <>
-        <div style={{ position: 'absolute', left: frame.x, top: frame.y, width: frame.width, height: frame.height, border: '1px solid rgba(0, 0, 0, 0.25)' }} />
-
-        <div data-drag='move' style={{ position: 'absolute', left: cropFrame.x, top: cropFrame.y, width: cropFrame.width, height: cropFrame.height, border, cursor: 'move' }} onMouseDown={onStart} draggable={false} />
+        <div data-drag='move' style={{ position: 'absolute', left: cropFrame.x, top: cropFrame.y, width: cropFrame.width, height: cropFrame.height, border, cursor: 'move' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
 
-        <div data-drag='left' style={{ position: 'absolute', left: cropFrame.x - d, top: cropFrame.y + d, width: 4 * d, height: cropFrame.height - d, background: transparent, cursor: 'w-resize' }} onMouseDown={onStart} draggable={false} />
-        <div data-drag='right' style={{ position: 'absolute', left: rectCrop.r - 2 * d, top: cropFrame.y, width: 4 * d, height: cropFrame.height - d, background: transparent, cursor: 'w-resize' }} onMouseDown={onStart} draggable={false} />
-        <div data-drag='top' style={{ position: 'absolute', left: cropFrame.x - d, top: cropFrame.y - d, width: cropFrame.width + 2 * d, height: 4 * d, background: transparent, cursor: 'n-resize' }} onMouseDown={onStart} draggable={false} />
-        <div data-drag='bottom' style={{ position: 'absolute', left: cropFrame.x - d, top: rectCrop.b - 2 * d, width: cropFrame.width + 2 * d, height: 4 * d, background: transparent, cursor: 'n-resize' }} onMouseDown={onStart} draggable={false} />
+        <div data-drag='left' style={{ position: 'absolute', left: cropFrame.x - d, top: cropFrame.y + d, width: 4 * d, height: cropFrame.height - d, background: transparent, cursor: 'w-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
+        <div data-drag='right' style={{ position: 'absolute', left: rectCrop.r - 2 * d, top: cropFrame.y, width: 4 * d, height: cropFrame.height - d, background: transparent, cursor: 'w-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
+        <div data-drag='top' style={{ position: 'absolute', left: cropFrame.x - d, top: cropFrame.y - d, width: cropFrame.width + 2 * d, height: 4 * d, background: transparent, cursor: 'n-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
+        <div data-drag='bottom' style={{ position: 'absolute', left: cropFrame.x - d, top: rectCrop.b - 2 * d, width: cropFrame.width + 2 * d, height: 4 * d, background: transparent, cursor: 'n-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
 
-        <div data-drag='top, left' style={{ position: 'absolute', left: rectCrop.l - d, top: rectCrop.t - d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'nw-resize' }} onMouseDown={onStart} draggable={false} />
-        <div data-drag='bottom, right' style={{ position: 'absolute', left: rectCrop.r - 2 * d, top: rectCrop.b - 2 * d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'nw-resize' }} onMouseDown={onStart} draggable={false} />
-        <div data-drag='top, right' style={{ position: 'absolute', left: rectCrop.r - 2 * d, top: rectCrop.t - d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'ne-resize' }} onMouseDown={onStart} draggable={false} />
-        <div data-drag='bottom, left' style={{ position: 'absolute', left: rectCrop.l - d, top: rectCrop.b - 2 * d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'ne-resize' }} onMouseDown={onStart} draggable={false} />
+        <div data-drag='top, left' style={{ position: 'absolute', left: rectCrop.l - d, top: rectCrop.t - d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'nw-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
+        <div data-drag='bottom, right' style={{ position: 'absolute', left: rectCrop.r - 2 * d, top: rectCrop.b - 2 * d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'nw-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
+        <div data-drag='top, right' style={{ position: 'absolute', left: rectCrop.r - 2 * d, top: rectCrop.t - d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'ne-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
+        <div data-drag='bottom, left' style={{ position: 'absolute', left: rectCrop.l - d, top: rectCrop.b - 2 * d, width: 4 * d, height: 4 * d, background: transparent, cursor: 'ne-resize' }} onMouseDown={onStart} draggable={false} onContextMenu={contextMenu} />
     </>;
 }
 
 function toRect(viewport: Viewport) {
-    return { l: viewport.x, t: viewport.y, r: viewport.x + viewport.width, b: viewport.y + viewport.height };
+    return { l: viewport.x, t: viewport.y, r: viewport.x + viewport.width - 1, b: viewport.y + viewport.height - 1 };
 }
 
 function getViewportFrame(srcWidth: number, srcHeight: number, w: number, h: number): Viewport {

+ 73 - 4
src/mol-plugin/util/viewport-screenshot.ts

@@ -13,6 +13,7 @@ import { PluginComponent } from '../../mol-plugin-state/component';
 import { PluginStateObject } from '../../mol-plugin-state/objects';
 import { StateSelection } from '../../mol-state';
 import { RuntimeContext, Task } from '../../mol-task';
+import { Color } from '../../mol-util/color';
 import { download } from '../../mol-util/download';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { SetUtils } from '../../mol-util/set';
@@ -51,6 +52,7 @@ class ViewportScreenshotHelper extends PluginComponent {
             }),
             transparent: PD.Boolean(false),
             axes: CameraHelperParams.axes,
+            autoCrop: PD.Boolean(true, { isHidden: true })
         };
     }
     private _params: ReturnType<ViewportScreenshotHelper['createParams']> = void 0 as any;
@@ -63,9 +65,10 @@ class ViewportScreenshotHelper extends PluginComponent {
         values: this.ev.behavior<ViewportScreenshotHelperParams>({
             transparent: this.params.transparent.defaultValue,
             axes: { name: 'off', params: {} },
-            resolution: this.params.resolution.defaultValue
+            resolution: this.params.resolution.defaultValue,
+            autoCrop: this.params.autoCrop.defaultValue
         }),
-        relativeCrop: this.ev.behavior<Viewport>({ x: 0.33, y: 0.33, width: 0.45, height: 0.45 })
+        relativeCrop: this.ev.behavior<Viewport>({ x: 0, y: 0, width: 1, height: 1 })
     };
 
     get values() {
@@ -135,7 +138,66 @@ class ViewportScreenshotHelper extends PluginComponent {
         return canvas;
     }();
 
-    getPreview(maxDim = 640) {
+    private previewData = {
+        image: { data: new Uint8ClampedArray(1), width: 1, height: 0 } as ImageData,
+        background: Color(0),
+        transparent: false
+    };
+
+    resetCrop() {
+        this.behaviors.relativeCrop.next({ x: 0, y: 0, width: 1, height: 1 });
+    }
+
+    autocrop(relativePadding = 0.1) {
+        const { data, width, height } = this.previewData.image;
+        const bgColor = this.previewData.transparent ? this.previewData.background : 0xff000000 | this.previewData.background;
+
+        let l = width, r = 0, t = height, b = 0;
+
+        for (let j = 0; j < height; j++) {
+            const jj = j * width;
+            for (let i = 0; i < width; i++) {
+                const o = 4 * (jj + i);
+                const c = (data[o] << 16) | (data[o + 1] << 8) | (data[o + 2]) | (data[o + 3] << 24);
+
+                if (c === bgColor) continue;
+
+                if (i < l) l = i;
+                if (i > r) r = i;
+                if (j < t) t = j;
+                if (j > b) b = j;
+            }
+        }
+
+        if (l > r) {
+            const x = l;
+            l = r;
+            r = x;
+        }
+
+        if (t > b) {
+            const x = t;
+            t = b;
+            b = x;
+        }
+
+        const tw = r - l + 1, th = b - t + 1;
+        l -= relativePadding * tw;
+        r += relativePadding * tw;
+        t -= relativePadding * th;
+        b += relativePadding * th;
+
+        const crop: Viewport = {
+            x: Math.max(0, l / width),
+            y: Math.max(0, t / height),
+            width: Math.min(1, (r - l + 1) / width),
+            height: Math.min(1, (b - t + 1) / height)
+        };
+
+        this.behaviors.relativeCrop.next(crop);
+    }
+
+    getPreview(maxDim = 320) {
         const { width, height } = this.getSize();
         if (width <= 0 || height <= 0) return;
 
@@ -150,19 +212,26 @@ class ViewportScreenshotHelper extends PluginComponent {
             w = Math.round(maxDim * f);
         }
 
+        const canvasProps = this.plugin.canvas3d!.props;
         this.previewPass.setProps({
             cameraHelper: { axes: this.values.axes },
             transparentBackground: this.values.transparent,
             // TODO: optimize because this creates a copy of a large object!
-            postprocessing: this.plugin.canvas3d!.props.postprocessing
+            postprocessing: canvasProps.postprocessing
         });
         const imageData = this.previewPass.getImageData(w, h);
         const canvas = this.previewCanvas;
         canvas.width = imageData.width;
         canvas.height = imageData.height;
+
+        this.previewData.image = imageData;
+        this.previewData.background = canvasProps.renderer.backgroundColor;
+        this.previewData.transparent = this.values.transparent;
+
         const canvasCtx = canvas.getContext('2d');
         if (!canvasCtx) throw new Error('Could not create canvas 2d context');
         canvasCtx.putImageData(imageData, 0, 0);
+        if (this.values.autoCrop) this.autocrop();
         return { canvas, width: w, height: h };
     }