Adds option to crop to bounding box on save

This commit is contained in:
psychedelicious 2022-11-20 21:39:02 +11:00 committed by blessedcoolant
parent f68702520b
commit 896c2532c7
6 changed files with 76 additions and 24 deletions

View File

@ -3,6 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { import {
clearCanvasHistory, clearCanvasHistory,
setShouldAutoSave, setShouldAutoSave,
setShouldCropToBoundingBoxOnSave,
setShouldDarkenOutsideBoundingBox, setShouldDarkenOutsideBoundingBox,
setShouldShowCanvasDebugInfo, setShouldShowCanvasDebugInfo,
setShouldShowGrid, setShouldShowGrid,
@ -22,21 +23,23 @@ export const canvasControlsSelector = createSelector(
[canvasSelector], [canvasSelector],
(canvas) => { (canvas) => {
const { const {
shouldDarkenOutsideBoundingBox,
shouldShowIntermediates,
shouldShowGrid,
shouldSnapToGrid,
shouldAutoSave, shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldDarkenOutsideBoundingBox,
shouldShowCanvasDebugInfo, shouldShowCanvasDebugInfo,
shouldShowGrid,
shouldShowIntermediates,
shouldSnapToGrid,
} = canvas; } = canvas;
return { return {
shouldShowGrid,
shouldSnapToGrid,
shouldAutoSave, shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldDarkenOutsideBoundingBox, shouldDarkenOutsideBoundingBox,
shouldShowIntermediates,
shouldShowCanvasDebugInfo, shouldShowCanvasDebugInfo,
shouldShowGrid,
shouldShowIntermediates,
shouldSnapToGrid,
}; };
}, },
{ {
@ -49,12 +52,13 @@ export const canvasControlsSelector = createSelector(
const IAICanvasSettingsButtonPopover = () => { const IAICanvasSettingsButtonPopover = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { const {
shouldShowIntermediates,
shouldShowGrid,
shouldSnapToGrid,
shouldAutoSave, shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldDarkenOutsideBoundingBox, shouldDarkenOutsideBoundingBox,
shouldShowCanvasDebugInfo, shouldShowCanvasDebugInfo,
shouldShowGrid,
shouldShowIntermediates,
shouldSnapToGrid,
} = useAppSelector(canvasControlsSelector); } = useAppSelector(canvasControlsSelector);
return ( return (
@ -98,6 +102,13 @@ const IAICanvasSettingsButtonPopover = () => {
isChecked={shouldAutoSave} isChecked={shouldAutoSave}
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))} onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
/> />
<IAICheckbox
label="Crop to Bounding Box"
isChecked={shouldCropToBoundingBoxOnSave}
onChange={(e) =>
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
}
/>
<IAICheckbox <IAICheckbox
label="Show Canvas Debug Info" label="Show Canvas Debug Info"
isChecked={shouldShowCanvasDebugInfo} isChecked={shouldShowCanvasDebugInfo}

View File

@ -38,12 +38,13 @@ export const selector = createSelector(
[systemSelector, canvasSelector, isStagingSelector], [systemSelector, canvasSelector, isStagingSelector],
(system, canvas, isStaging) => { (system, canvas, isStaging) => {
const { isProcessing } = system; const { isProcessing } = system;
const { tool } = canvas; const { tool, shouldCropToBoundingBoxOnSave } = canvas;
return { return {
isProcessing, isProcessing,
isStaging, isStaging,
tool, tool,
shouldCropToBoundingBoxOnSave,
}; };
}, },
{ {
@ -55,7 +56,8 @@ export const selector = createSelector(
const IAICanvasOutpaintingControls = () => { const IAICanvasOutpaintingControls = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isProcessing, isStaging, tool } = useAppSelector(selector); const { isProcessing, isStaging, tool, shouldCropToBoundingBoxOnSave } =
useAppSelector(selector);
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
const { openUploader } = useImageUploader(); const { openUploader } = useImageUploader();
@ -164,7 +166,8 @@ const IAICanvasOutpaintingControls = () => {
const handleSaveToGallery = () => { const handleSaveToGallery = () => {
dispatch( dispatch(
mergeAndUploadCanvas({ mergeAndUploadCanvas({
cropVisible: true, cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldSaveToGallery: true, shouldSaveToGallery: true,
}) })
); );
@ -173,7 +176,8 @@ const IAICanvasOutpaintingControls = () => {
const handleCopyImageToClipboard = () => { const handleCopyImageToClipboard = () => {
dispatch( dispatch(
mergeAndUploadCanvas({ mergeAndUploadCanvas({
cropVisible: true, cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldCopy: true, shouldCopy: true,
}) })
); );
@ -182,7 +186,8 @@ const IAICanvasOutpaintingControls = () => {
const handleDownloadAsImage = () => { const handleDownloadAsImage = () => {
dispatch( dispatch(
mergeAndUploadCanvas({ mergeAndUploadCanvas({
cropVisible: true, cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldDownload: true, shouldDownload: true,
}) })
); );

View File

@ -64,6 +64,7 @@ const initialCanvasState: CanvasState = {
minimumStageScale: 1, minimumStageScale: 1,
pastLayerStates: [], pastLayerStates: [],
shouldAutoSave: false, shouldAutoSave: false,
shouldCropToBoundingBoxOnSave: true,
shouldDarkenOutsideBoundingBox: false, shouldDarkenOutsideBoundingBox: false,
shouldLockBoundingBox: false, shouldLockBoundingBox: false,
shouldPreserveMaskedArea: false, shouldPreserveMaskedArea: false,
@ -676,6 +677,12 @@ export const canvasSlice = createSlice({
setShouldShowCanvasDebugInfo: (state, action: PayloadAction<boolean>) => { setShouldShowCanvasDebugInfo: (state, action: PayloadAction<boolean>) => {
state.shouldShowCanvasDebugInfo = action.payload; state.shouldShowCanvasDebugInfo = action.payload;
}, },
setShouldCropToBoundingBoxOnSave: (
state,
action: PayloadAction<boolean>
) => {
state.shouldCropToBoundingBoxOnSave = action.payload;
},
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => { setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
state.pastLayerStates.push({ state.pastLayerStates.push({
...state.layerState, ...state.layerState,
@ -737,6 +744,7 @@ export const {
setMaskColor, setMaskColor,
setMergedCanvas, setMergedCanvas,
setShouldAutoSave, setShouldAutoSave,
setShouldCropToBoundingBoxOnSave,
setShouldDarkenOutsideBoundingBox, setShouldDarkenOutsideBoundingBox,
setShouldLockBoundingBox, setShouldLockBoundingBox,
setShouldPreserveMaskedArea, setShouldPreserveMaskedArea,

View File

@ -102,6 +102,7 @@ export interface CanvasState {
minimumStageScale: number; minimumStageScale: number;
pastLayerStates: CanvasLayerState[]; pastLayerStates: CanvasLayerState[];
shouldAutoSave: boolean; shouldAutoSave: boolean;
shouldCropToBoundingBoxOnSave: boolean;
shouldDarkenOutsideBoundingBox: boolean; shouldDarkenOutsideBoundingBox: boolean;
shouldLockBoundingBox: boolean; shouldLockBoundingBox: boolean;
shouldPreserveMaskedArea: boolean; shouldPreserveMaskedArea: boolean;

View File

@ -15,9 +15,11 @@ import {
} from 'features/system/store/systemSlice'; } from 'features/system/store/systemSlice';
import { addImage } from 'features/gallery/store/gallerySlice'; import { addImage } from 'features/gallery/store/gallerySlice';
import { setMergedCanvas } from '../canvasSlice'; import { setMergedCanvas } from '../canvasSlice';
import { CanvasState } from '../canvasTypes';
type MergeAndUploadCanvasConfig = { type MergeAndUploadCanvasConfig = {
cropVisible?: boolean; cropVisible?: boolean;
cropToBoundingBox?: boolean;
shouldSaveToGallery?: boolean; shouldSaveToGallery?: boolean;
shouldDownload?: boolean; shouldDownload?: boolean;
shouldCopy?: boolean; shouldCopy?: boolean;
@ -26,6 +28,7 @@ type MergeAndUploadCanvasConfig = {
const defaultConfig: MergeAndUploadCanvasConfig = { const defaultConfig: MergeAndUploadCanvasConfig = {
cropVisible: false, cropVisible: false,
cropToBoundingBox: false,
shouldSaveToGallery: false, shouldSaveToGallery: false,
shouldDownload: false, shouldDownload: false,
shouldCopy: false, shouldCopy: false,
@ -37,6 +40,7 @@ export const mergeAndUploadCanvas =
async (dispatch, getState) => { async (dispatch, getState) => {
const { const {
cropVisible, cropVisible,
cropToBoundingBox,
shouldSaveToGallery, shouldSaveToGallery,
shouldDownload, shouldDownload,
shouldCopy, shouldCopy,
@ -48,7 +52,12 @@ export const mergeAndUploadCanvas =
const state = getState() as RootState; const state = getState() as RootState;
const stageScale = state.canvas.stageScale; const {
stageScale,
boundingBoxCoordinates,
boundingBoxDimensions,
stageCoordinates,
} = state.canvas as CanvasState;
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
@ -61,7 +70,11 @@ export const mergeAndUploadCanvas =
const { dataURL, boundingBox: originalBoundingBox } = layerToDataURL( const { dataURL, boundingBox: originalBoundingBox } = layerToDataURL(
canvasBaseLayer, canvasBaseLayer,
stageScale stageScale,
stageCoordinates,
cropToBoundingBox
? { ...boundingBoxCoordinates, ...boundingBoxDimensions }
: undefined
); );
if (!dataURL) { if (!dataURL) {

View File

@ -1,6 +1,12 @@
import Konva from 'konva'; import Konva from 'konva';
import { IRect, Vector2d } from 'konva/lib/types';
const layerToDataURL = (layer: Konva.Layer, stageScale: number) => { const layerToDataURL = (
layer: Konva.Layer,
stageScale: number,
stageCoordinates: Vector2d,
boundingBox?: IRect
) => {
const tempScale = layer.scale(); const tempScale = layer.scale();
const relativeClientRect = layer.getClientRect({ const relativeClientRect = layer.getClientRect({
@ -14,13 +20,21 @@ const layerToDataURL = (layer: Konva.Layer, stageScale: number) => {
}); });
const { x, y, width, height } = layer.getClientRect(); const { x, y, width, height } = layer.getClientRect();
const dataURLBoundingBox = boundingBox
? {
x: Math.round(boundingBox.x + stageCoordinates.x),
y: Math.round(boundingBox.y + stageCoordinates.y),
width: Math.round(boundingBox.width),
height: Math.round(boundingBox.height),
}
: {
x: Math.round(x),
y: Math.round(y),
width: Math.round(width),
height: Math.round(height),
};
const dataURL = layer.toDataURL({ const dataURL = layer.toDataURL(dataURLBoundingBox);
x: Math.round(x),
y: Math.round(y),
width: Math.round(width),
height: Math.round(height),
});
// Unscale the canvas // Unscale the canvas
layer.scale(tempScale); layer.scale(tempScale);