mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds option to crop to bounding box on save
This commit is contained in:
parent
f68702520b
commit
896c2532c7
@ -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}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user