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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,12 @@
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 relativeClientRect = layer.getClientRect({
@ -14,13 +20,21 @@ const layerToDataURL = (layer: Konva.Layer, stageScale: number) => {
});
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({
x: Math.round(x),
y: Math.round(y),
width: Math.round(width),
height: Math.round(height),
});
const dataURL = layer.toDataURL(dataURLBoundingBox);
// Unscale the canvas
layer.scale(tempScale);