Skip to content

Commit

Permalink
feat(ui): image ctx -> New from Image -> Canvas as Raster/Control Layer
Browse files Browse the repository at this point in the history
  • Loading branch information
psychedelicious authored and hipsterusername committed Oct 26, 2024
1 parent 56222a8 commit db1c5a9
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 11 deletions.
2 changes: 2 additions & 0 deletions invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,8 @@
"controlLayer": "Control Layer",
"inpaintMask": "Inpaint Mask",
"regionalGuidance": "Regional Guidance",
"canvasAsRasterLayer": "$t(controlLayers.canvas) as $t(controlLayers.rasterLayer)",
"canvasAsControlLayer": "$t(controlLayers.canvas) as $t(controlLayers.controlLayer)",
"referenceImage": "Reference Image",
"regionalReferenceImage": "Regional Reference Image",
"globalReferenceImage": "Global Reference Image",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import { useCallback } from 'react';
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
import { isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig } from 'services/api/types';
import type { Equals } from 'tsafe';
import { assert } from 'tsafe';

export const selectDefaultControlAdapter = createSelector(
selectModelConfigsQuery,
Expand Down Expand Up @@ -194,18 +196,31 @@ export const useNewCanvasFromImage = () => {
const bboxRect = useAppSelector(selectBboxRect);
const base = useAppSelector(selectBboxModelBase);
const func = useCallback(
(imageDTO: ImageDTO) => {
(imageDTO: ImageDTO, type: CanvasRasterLayerState['type'] | CanvasControlLayerState['type']) => {
// Calculate the new bbox dimensions to fit the image's aspect ratio at the optimal size
const ratio = imageDTO.width / imageDTO.height;
const optimalDimension = getOptimalDimension(base);
const { width, height } = calculateNewSize(ratio, optimalDimension ** 2, base);

// The overrides need to include the layer's ID so we can transform the layer it is initialized
const overrides = {
id: getPrefixedId('raster_layer'),
position: { x: bboxRect.x, y: bboxRect.y },
objects: [imageDTOToImageObject(imageDTO)],
} satisfies Partial<CanvasRasterLayerState>;
let overrides: Partial<CanvasRasterLayerState> | Partial<CanvasControlLayerState>;

if (type === 'raster_layer') {
overrides = {
id: getPrefixedId('raster_layer'),
position: { x: bboxRect.x, y: bboxRect.y },
objects: [imageDTOToImageObject(imageDTO)],
} satisfies Partial<CanvasRasterLayerState>;
} else if (type === 'control_layer') {
overrides = {
id: getPrefixedId('control_layer'),
position: { x: bboxRect.x, y: bboxRect.y },
objects: [imageDTOToImageObject(imageDTO)],
} satisfies Partial<CanvasControlLayerState>;
} else {
// Catch unhandled types
assert<Equals<typeof type, never>>(false);
}

CanvasEntityAdapterBase.registerInitCallback(async (adapter) => {
// Skip the callback if the adapter is not the one we are creating
Expand All @@ -222,7 +237,16 @@ export const useNewCanvasFromImage = () => {
dispatch(canvasReset());
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
dispatch(rasterLayerAdded({ overrides, isSelected: true }));

// The type casts are safe because the type is checked above
if (type === 'raster_layer') {
dispatch(rasterLayerAdded({ overrides: overrides as Partial<CanvasRasterLayerState>, isSelected: true }));
} else if (type === 'control_layer') {
dispatch(controlLayerAdded({ overrides: overrides as Partial<CanvasControlLayerState>, isSelected: true }));
} else {
// Catch unhandled types
assert<Equals<typeof type, never>>(false);
}
},
[base, bboxRect.x, bboxRect.y, dispatch]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,19 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => {
const newRegionalGuidanceFromImage = useNewRegionalGuidanceFromImage();
const newCanvasFromImage = useNewCanvasFromImage();

const onClickNewCanvasFromImage = useCallback(() => {
newCanvasFromImage(imageDTO);
const onClickNewCanvasWithRasterLayerFromImage = useCallback(() => {
newCanvasFromImage(imageDTO, 'raster_layer');
dispatch(setActiveTab('canvas'));
imageViewer.close();
toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToCanvas'),
status: 'success',
});
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);

const onClickNewCanvasWithControlLayerFromImage = useCallback(() => {
newCanvasFromImage(imageDTO, 'control_layer');
dispatch(setActiveTab('canvas'));
imageViewer.close();
toast({
Expand Down Expand Up @@ -98,8 +109,15 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => {
<SubMenuButtonContent label="New from Image" />
</MenuButton>
<MenuList {...subMenu.menuListProps}>
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasFromImage} isDisabled={isBusy}>
{t('controlLayers.canvas')}
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasWithRasterLayerFromImage} isDisabled={isBusy}>
{t('controlLayers.canvasAsRasterLayer')}
</MenuItem>
<MenuItem
icon={<PiFileBold />}
onClickCapture={onClickNewCanvasWithControlLayerFromImage}
isDisabled={isBusy}
>
{t('controlLayers.canvasAsControlLayer')}
</MenuItem>
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}>
{t('controlLayers.inpaintMask')}
Expand Down

0 comments on commit db1c5a9

Please sign in to comment.