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 {
|
||||
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}
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -102,6 +102,7 @@ export interface CanvasState {
|
||||
minimumStageScale: number;
|
||||
pastLayerStates: CanvasLayerState[];
|
||||
shouldAutoSave: boolean;
|
||||
shouldCropToBoundingBoxOnSave: boolean;
|
||||
shouldDarkenOutsideBoundingBox: boolean;
|
||||
shouldLockBoundingBox: boolean;
|
||||
shouldPreserveMaskedArea: boolean;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user