mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): staging area works
This commit is contained in:
parent
30d318d021
commit
4668ea449b
@ -5,9 +5,10 @@ import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
caImageChanged,
|
||||
ipaImageChanged,
|
||||
layerAddedFromImage,
|
||||
layerAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasLayerState } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||
@ -106,7 +107,13 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(layerAddedFromImage({ imageObject: imageDTOToImageObject(activeData.payload.imageDTO) }));
|
||||
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
||||
const { x, y } = getState().canvasV2.bbox.rect;
|
||||
const overrides: Partial<CanvasLayerState> = {
|
||||
objects: [imageObject],
|
||||
position: { x, y },
|
||||
};
|
||||
dispatch(layerAdded({ overrides, isSelected: true }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ export const AddLayerButton = memo(() => {
|
||||
dispatch(rgAdded());
|
||||
}, [dispatch]);
|
||||
const addRasterLayer = useCallback(() => {
|
||||
dispatch(layerAdded());
|
||||
dispatch(layerAdded({ isSelected: true }));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
|
||||
import type { Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { atom } from 'nanostores';
|
||||
@ -23,6 +24,7 @@ export class CanvasBbox {
|
||||
static CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
|
||||
static NO_ANCHORS: string[] = [];
|
||||
|
||||
parent: CanvasPreview;
|
||||
manager: CanvasManager;
|
||||
|
||||
konva: {
|
||||
@ -31,8 +33,9 @@ export class CanvasBbox {
|
||||
transformer: Konva.Transformer;
|
||||
};
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
constructor(parent: CanvasPreview) {
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
|
||||
// transforming the bbox.
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
|
@ -2,6 +2,7 @@ import { Mutex } from 'async-mutex';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import type { CanvasStagingArea } from 'features/controlLayers/konva/CanvasStagingArea';
|
||||
import { FILTER_MAP } from 'features/controlLayers/konva/filters';
|
||||
import { loadImage } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasImageState, GetLoggingContext } from 'features/controlLayers/store/types';
|
||||
@ -19,7 +20,7 @@ export class CanvasImageRenderer {
|
||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImageRenderer.TYPE}_placeholder-text`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasObjectRenderer;
|
||||
parent: CanvasObjectRenderer | CanvasStagingArea;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
@ -36,7 +37,7 @@ export class CanvasImageRenderer {
|
||||
isError: boolean = false;
|
||||
mutex = new Mutex();
|
||||
|
||||
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
|
||||
constructor(state: CanvasImageState, parent: CanvasObjectRenderer | CanvasStagingArea) {
|
||||
const { id, image } = state;
|
||||
const { width, height } = image;
|
||||
this.id = id;
|
||||
@ -97,18 +98,16 @@ export class CanvasImageRenderer {
|
||||
this.onFailedToLoadImage();
|
||||
return;
|
||||
}
|
||||
// Load the thumbnail first, but let the image load in parallel
|
||||
loadImage(imageDTO.thumbnail_url)
|
||||
.then((thumbnailElement) => {
|
||||
this.thumbnailElement = thumbnailElement;
|
||||
this.mutex.runExclusive(this.updateImageElement);
|
||||
})
|
||||
.catch(this.onFailedToLoadImage);
|
||||
loadImage(imageDTO.image_url)
|
||||
.then((imageElement) => {
|
||||
this.imageElement = imageElement;
|
||||
this.mutex.runExclusive(this.updateImageElement);
|
||||
this.updateImageElement();
|
||||
})
|
||||
.catch(this.onFailedToLoadImage);
|
||||
|
||||
this.imageElement = await loadImage(imageDTO.image_url);
|
||||
await this.updateImageElement();
|
||||
} catch {
|
||||
this.onFailedToLoadImage();
|
||||
}
|
||||
@ -123,21 +122,29 @@ export class CanvasImageRenderer {
|
||||
this.konva.placeholder.group.visible(true);
|
||||
};
|
||||
|
||||
updateImageElement = () => {
|
||||
updateImageElement = async () => {
|
||||
const release = await this.mutex.acquire();
|
||||
|
||||
try {
|
||||
const element = this.imageElement ?? this.thumbnailElement;
|
||||
const { width, height } = this.state.image;
|
||||
|
||||
if (element) {
|
||||
if (this.konva.image && this.konva.image.image() !== element) {
|
||||
if (this.konva.image) {
|
||||
this.log.trace('Updating Konva image attrs');
|
||||
this.konva.image.setAttrs({
|
||||
image: element,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
this.log.trace('Creating new Konva image');
|
||||
this.konva.image = new Konva.Image({
|
||||
name: CanvasImageRenderer.IMAGE_NAME,
|
||||
listening: false,
|
||||
image: element,
|
||||
width: this.state.image.width,
|
||||
height: this.state.image.height,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
this.konva.group.add(this.konva.image);
|
||||
}
|
||||
@ -150,10 +157,16 @@ export class CanvasImageRenderer {
|
||||
this.konva.image.filters([]);
|
||||
}
|
||||
|
||||
this.konva.placeholder.rect.setAttrs({ width, height });
|
||||
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
|
||||
|
||||
this.isLoading = false;
|
||||
this.isError = false;
|
||||
this.konva.placeholder.group.visible(false);
|
||||
}
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
};
|
||||
|
||||
update = async (state: CanvasImageState, force = false): Promise<boolean> => {
|
||||
@ -173,8 +186,6 @@ export class CanvasImageRenderer {
|
||||
this.konva.image?.clearCache();
|
||||
this.konva.image?.filters([]);
|
||||
}
|
||||
this.konva.placeholder.rect.setAttrs({ width, height });
|
||||
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
|
||||
this.state = state;
|
||||
return true;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import type { CanvasBrushLineRenderer } from 'features/controlLayers/konva/Canva
|
||||
import type { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
|
||||
import type { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import type { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
|
||||
@ -19,7 +18,6 @@ import {
|
||||
nanoid,
|
||||
} from 'features/controlLayers/konva/util';
|
||||
import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker';
|
||||
import { $lastProgressEvent, $shouldShowStagedImage } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type {
|
||||
CanvasControlAdapterState,
|
||||
CanvasEntityIdentifier,
|
||||
@ -49,14 +47,12 @@ import type { ImageCategory, ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { CanvasBackground } from './CanvasBackground';
|
||||
import { CanvasBbox } from './CanvasBbox';
|
||||
import { CanvasControlAdapter } from './CanvasControlAdapter';
|
||||
import { CanvasLayerAdapter } from './CanvasLayerAdapter';
|
||||
import { CanvasMaskAdapter } from './CanvasMaskAdapter';
|
||||
import { CanvasPreview } from './CanvasPreview';
|
||||
import { CanvasStagingArea } from './CanvasStagingArea';
|
||||
import { CanvasStateApi } from './CanvasStateApi';
|
||||
import { CanvasTool } from './CanvasTool';
|
||||
import { setStageEventHandlers } from './events';
|
||||
|
||||
// type Extents = {
|
||||
@ -159,15 +155,6 @@ export class CanvasManager {
|
||||
|
||||
this.transformingEntity = new PubSub<CanvasEntityIdentifier | null>(null);
|
||||
this.toolState = new PubSub(this.stateApi.getToolState());
|
||||
this.currentFill = new PubSub(this.getCurrentFill());
|
||||
this.selectedEntityIdentifier = new PubSub(
|
||||
this.stateApi.getState().selectedEntityIdentifier,
|
||||
(a, b) => a?.id === b?.id
|
||||
);
|
||||
this.selectedEntity = new PubSub(
|
||||
this.getSelectedEntity(),
|
||||
(a, b) => a?.state === b?.state && a?.adapter === b?.adapter
|
||||
);
|
||||
|
||||
this._prevState = this.stateApi.getState();
|
||||
|
||||
@ -187,13 +174,8 @@ export class CanvasManager {
|
||||
uploadImage,
|
||||
};
|
||||
|
||||
this.preview = new CanvasPreview(
|
||||
new CanvasBbox(this),
|
||||
new CanvasTool(this),
|
||||
new CanvasStagingArea(this),
|
||||
new CanvasProgressPreview(this)
|
||||
);
|
||||
this.stage.add(this.preview.layer);
|
||||
this.preview = new CanvasPreview(this);
|
||||
this.stage.add(this.preview.getLayer());
|
||||
|
||||
this.background = new CanvasBackground(this);
|
||||
this.stage.add(this.background.konva.layer);
|
||||
@ -226,6 +208,16 @@ export class CanvasManager {
|
||||
this.log.error('Worker message error');
|
||||
};
|
||||
|
||||
this.currentFill = new PubSub(this.getCurrentFill());
|
||||
this.selectedEntityIdentifier = new PubSub(
|
||||
this.stateApi.getState().selectedEntityIdentifier,
|
||||
(a, b) => a?.id === b?.id
|
||||
);
|
||||
this.selectedEntity = new PubSub(
|
||||
this.getSelectedEntity(),
|
||||
(a, b) => a?.state === b?.state && a?.adapter === b?.adapter
|
||||
);
|
||||
|
||||
this.inpaintMask = new CanvasMaskAdapter(this.stateApi.getInpaintMaskState(), this);
|
||||
this.stage.add(this.inpaintMask.konva.layer);
|
||||
}
|
||||
@ -249,10 +241,6 @@ export class CanvasManager {
|
||||
this._worker.postMessage(task, [data.buffer]);
|
||||
}
|
||||
|
||||
async renderProgressPreview() {
|
||||
await this.preview.progressPreview.render(this.stateApi.$lastProgressEvent.get());
|
||||
}
|
||||
|
||||
async renderControlAdapters() {
|
||||
const { entities } = this.stateApi.getControlAdaptersState();
|
||||
|
||||
@ -291,7 +279,7 @@ export class CanvasManager {
|
||||
this.regions.get(rg.id)?.konva.layer.zIndex(++zIndex);
|
||||
}
|
||||
this.inpaintMask.konva.layer.zIndex(++zIndex);
|
||||
this.preview.layer.zIndex(++zIndex);
|
||||
this.preview.getLayer().zIndex(++zIndex);
|
||||
}
|
||||
|
||||
fitStageToContainer() {
|
||||
@ -611,25 +599,6 @@ export class CanvasManager {
|
||||
|
||||
const unsubscribeRenderer = this._store.subscribe(this.render);
|
||||
|
||||
// When we this flag, we need to render the staging area
|
||||
const unsubscribeShouldShowStagedImage = $shouldShowStagedImage.subscribe(
|
||||
async (shouldShowStagedImage, prevShouldShowStagedImage) => {
|
||||
if (shouldShowStagedImage !== prevShouldShowStagedImage) {
|
||||
this.log.debug('Rendering staging area');
|
||||
await this.preview.stagingArea.render();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const unsubscribeLastProgressEvent = $lastProgressEvent.subscribe(
|
||||
async (lastProgressEvent, prevLastProgressEvent) => {
|
||||
if (lastProgressEvent !== prevLastProgressEvent) {
|
||||
this.log.debug('Rendering progress image');
|
||||
await this.preview.progressPreview.render(lastProgressEvent);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.log.debug('First render of konva stage');
|
||||
this.preview.tool.render();
|
||||
this.render();
|
||||
@ -650,8 +619,6 @@ export class CanvasManager {
|
||||
this.preview.destroy();
|
||||
unsubscribeRenderer();
|
||||
unsubscribeListeners();
|
||||
unsubscribeShouldShowStagedImage();
|
||||
unsubscribeLastProgressEvent();
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
};
|
||||
|
@ -1,40 +1,51 @@
|
||||
import type { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasProgressImage } from 'features/controlLayers/konva/CanvasProgressImage';
|
||||
import Konva from 'konva';
|
||||
|
||||
import type { CanvasBbox } from './CanvasBbox';
|
||||
import type { CanvasStagingArea } from './CanvasStagingArea';
|
||||
import type { CanvasTool } from './CanvasTool';
|
||||
import { CanvasBbox } from './CanvasBbox';
|
||||
import { CanvasStagingArea } from './CanvasStagingArea';
|
||||
import { CanvasTool } from './CanvasTool';
|
||||
|
||||
export class CanvasPreview {
|
||||
manager: CanvasManager;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
tool: CanvasTool;
|
||||
bbox: CanvasBbox;
|
||||
stagingArea: CanvasStagingArea;
|
||||
progressPreview: CanvasProgressPreview;
|
||||
progressImage: CanvasProgressImage;
|
||||
|
||||
constructor(
|
||||
bbox: CanvasBbox,
|
||||
tool: CanvasTool,
|
||||
stagingArea: CanvasStagingArea,
|
||||
progressPreview: CanvasProgressPreview
|
||||
) {
|
||||
this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false });
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ listening: true, imageSmoothingEnabled: false }),
|
||||
};
|
||||
|
||||
this.stagingArea = stagingArea;
|
||||
this.layer.add(this.stagingArea.konva.group);
|
||||
this.stagingArea = new CanvasStagingArea(this);
|
||||
this.konva.layer.add(...this.stagingArea.getNodes());
|
||||
|
||||
this.bbox = bbox;
|
||||
this.layer.add(this.bbox.konva.group);
|
||||
this.progressImage = new CanvasProgressImage(this);
|
||||
this.konva.layer.add(...this.progressImage.getNodes());
|
||||
|
||||
this.tool = tool;
|
||||
this.layer.add(this.tool.konva.group);
|
||||
this.bbox = new CanvasBbox(this);
|
||||
this.konva.layer.add(this.bbox.konva.group);
|
||||
|
||||
this.progressPreview = progressPreview;
|
||||
this.layer.add(this.progressPreview.konva.group);
|
||||
this.tool = new CanvasTool(this);
|
||||
this.konva.layer.add(this.tool.konva.group);
|
||||
}
|
||||
|
||||
getLayer = () => {
|
||||
return this.konva.layer;
|
||||
};
|
||||
|
||||
destroy() {
|
||||
// this.stagingArea.destroy(); // TODO(psyche): implement destroy
|
||||
this.progressImage.destroy();
|
||||
// this.bbox.destroy(); // TODO(psyche): implement destroy
|
||||
this.tool.destroy();
|
||||
this.layer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { loadImage } from 'features/controlLayers/konva/util';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
|
||||
import { getPrefixedId, loadImage } from 'features/controlLayers/konva/util';
|
||||
import Konva from 'konva';
|
||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||
|
||||
export class CanvasProgressImage {
|
||||
static NAME_PREFIX = 'progress-image';
|
||||
@ -7,53 +11,86 @@ export class CanvasProgressImage {
|
||||
static IMAGE_NAME = `${CanvasProgressImage.NAME_PREFIX}_image`;
|
||||
|
||||
id: string;
|
||||
progressImageId: string | null;
|
||||
parent: CanvasPreview;
|
||||
manager: CanvasManager;
|
||||
|
||||
/**
|
||||
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
|
||||
*/
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
progressImageId: string | null = null;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
image: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
||||
};
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
isLoading: boolean = false;
|
||||
isError: boolean = false;
|
||||
imageElement: HTMLImageElement | null = null;
|
||||
|
||||
constructor(arg: { id: string }) {
|
||||
const { id } = arg;
|
||||
lastProgressEvent: InvocationDenoiseProgressEvent | null = null;
|
||||
|
||||
mutex: Mutex = new Mutex();
|
||||
|
||||
constructor(parent: CanvasPreview) {
|
||||
this.id = getPrefixedId(CanvasProgressImage.NAME_PREFIX);
|
||||
this.parent = parent;
|
||||
this.manager = parent.manager;
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasProgressImage.GROUP_NAME, listening: false }),
|
||||
image: null,
|
||||
};
|
||||
this.id = id;
|
||||
this.progressImageId = null;
|
||||
this.isLoading = false;
|
||||
this.isError = false;
|
||||
|
||||
this.manager.stateApi.$lastProgressEvent.listen((event) => {
|
||||
this.lastProgressEvent = event;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
async updateImageSource(
|
||||
progressImageId: string,
|
||||
dataURL: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
) {
|
||||
if (this.isLoading) {
|
||||
getNodes = () => {
|
||||
return [this.konva.group];
|
||||
};
|
||||
|
||||
render = async () => {
|
||||
const release = await this.mutex.acquire();
|
||||
|
||||
if (!this.lastProgressEvent) {
|
||||
this.konva.group.visible(false);
|
||||
this.imageElement = null;
|
||||
this.isLoading = false;
|
||||
this.isError = false;
|
||||
release();
|
||||
return;
|
||||
}
|
||||
|
||||
const { isStaging } = this.manager.stateApi.getSession();
|
||||
|
||||
if (!isStaging) {
|
||||
release();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
const { x, y } = this.manager.stateApi.getBbox().rect;
|
||||
const { dataURL, width, height } = this.lastProgressEvent.progress_image;
|
||||
try {
|
||||
const imageEl = await loadImage(dataURL);
|
||||
this.imageElement = await loadImage(dataURL);
|
||||
if (this.konva.image) {
|
||||
console.log('UPDATING PROGRESS IMAGE')
|
||||
this.konva.image.setAttrs({
|
||||
image: imageEl,
|
||||
image: this.imageElement,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
console.log('CREATING NEW PROGRESS IMAGE')
|
||||
this.konva.image = new Konva.Image({
|
||||
name: CanvasProgressImage.IMAGE_NAME,
|
||||
listening: false,
|
||||
image: imageEl,
|
||||
image: this.imageElement,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
@ -61,14 +98,19 @@ export class CanvasProgressImage {
|
||||
});
|
||||
this.konva.group.add(this.konva.image);
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.id = progressImageId;
|
||||
this.konva.group.visible(true);
|
||||
} catch {
|
||||
this.isError = true;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
destroy() {
|
||||
this.konva.group.destroy();
|
||||
destroy = () => {
|
||||
for (const unsubscribe of this.subscriptions) {
|
||||
unsubscribe();
|
||||
}
|
||||
this.konva.group.destroy();
|
||||
};
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasProgressImage } from 'features/controlLayers/konva/CanvasProgressImage';
|
||||
import Konva from 'konva';
|
||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||
|
||||
export class CanvasProgressPreview {
|
||||
static NAME_PREFIX = 'progress-preview';
|
||||
static GROUP_NAME = `${CanvasProgressPreview.NAME_PREFIX}_group`;
|
||||
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
progressImage: CanvasProgressImage;
|
||||
};
|
||||
manager: CanvasManager;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasProgressPreview.GROUP_NAME, listening: false }),
|
||||
progressImage: new CanvasProgressImage({ id: 'progress-image' }),
|
||||
};
|
||||
this.konva.group.add(this.konva.progressImage.konva.group);
|
||||
}
|
||||
|
||||
async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
|
||||
const bboxRect = this.manager.stateApi.getBbox().rect;
|
||||
const session = this.manager.stateApi.getSession();
|
||||
|
||||
if (lastProgressEvent && session.isStaging) {
|
||||
const { invocation, step, progress_image } = lastProgressEvent;
|
||||
const { dataURL } = progress_image;
|
||||
const { x, y, width, height } = bboxRect;
|
||||
const progressImageId = `${invocation.id}_${step}`;
|
||||
if (
|
||||
!this.konva.progressImage.isLoading &&
|
||||
!this.konva.progressImage.isError &&
|
||||
this.konva.progressImage.progressImageId !== progressImageId
|
||||
) {
|
||||
await this.konva.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
|
||||
this.konva.progressImage.konva.group.visible(true);
|
||||
}
|
||||
} else {
|
||||
this.konva.progressImage.konva.group.visible(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { GetLoggingContext, StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
@ -10,6 +11,7 @@ export class CanvasStagingArea {
|
||||
static GROUP_NAME = `${CanvasStagingArea.TYPE}_group`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasPreview;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
@ -19,9 +21,15 @@ export class CanvasStagingArea {
|
||||
image: CanvasImageRenderer | null;
|
||||
selectedImage: StagingAreaImage | null;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
/**
|
||||
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
|
||||
*/
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
constructor(parent: CanvasPreview) {
|
||||
this.id = getPrefixedId(CanvasStagingArea.TYPE);
|
||||
this.manager = manager;
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.log.debug('Creating staging area');
|
||||
@ -29,14 +37,17 @@ export class CanvasStagingArea {
|
||||
this.konva = { group: new Konva.Group({ name: CanvasStagingArea.GROUP_NAME, listening: false }) };
|
||||
this.image = null;
|
||||
this.selectedImage = null;
|
||||
|
||||
this.subscriptions.add(this.manager.stateApi.$shouldShowStagedImage.listen(this.render));
|
||||
}
|
||||
|
||||
render = async () => {
|
||||
const session = this.manager.stateApi.getSession();
|
||||
const bboxRect = this.manager.stateApi.getBbox().rect;
|
||||
const { rect } = this.manager.stateApi.getBbox();
|
||||
const shouldShowStagedImage = this.manager.stateApi.$shouldShowStagedImage.get();
|
||||
|
||||
this.selectedImage = session.stagedImages[session.selectedStagedImageIndex] ?? null;
|
||||
this.konva.group.position({ x: rect.x, y: rect.y });
|
||||
|
||||
if (this.selectedImage) {
|
||||
const { imageDTO, offsetX, offsetY } = this.selectedImage;
|
||||
@ -47,10 +58,6 @@ export class CanvasStagingArea {
|
||||
{
|
||||
id: 'staging-area-image',
|
||||
type: 'image',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
filters: [],
|
||||
image: {
|
||||
image_name: image_name,
|
||||
@ -63,11 +70,7 @@ export class CanvasStagingArea {
|
||||
this.konva.group.add(this.image.konva.group);
|
||||
}
|
||||
|
||||
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
|
||||
this.image.konva.image?.width(imageDTO.width);
|
||||
this.image.konva.image?.height(imageDTO.height);
|
||||
this.image.konva.group.x(bboxRect.x + offsetX);
|
||||
this.image.konva.group.y(bboxRect.y + offsetY);
|
||||
if (!this.image.isLoading && !this.image.isError) {
|
||||
await this.image.updateImageSource(imageDTO.image_name);
|
||||
this.manager.stateApi.$lastProgressEvent.set(null);
|
||||
}
|
||||
@ -77,6 +80,22 @@ export class CanvasStagingArea {
|
||||
}
|
||||
};
|
||||
|
||||
getNodes = () => {
|
||||
return [this.konva.group];
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
if (this.image) {
|
||||
this.image.destroy();
|
||||
}
|
||||
for (const unsubscribe of this.subscriptions) {
|
||||
unsubscribe();
|
||||
}
|
||||
for (const node of this.getNodes()) {
|
||||
node.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
|
||||
import {
|
||||
BRUSH_BORDER_INNER_COLOR,
|
||||
BRUSH_BORDER_OUTER_COLOR,
|
||||
@ -25,6 +26,7 @@ export class CanvasTool {
|
||||
static ERASER_INNER_BORDER_CIRCLE_NAME = `${CanvasTool.ERASER_NAME_PREFIX}_inner-border-circle`;
|
||||
static ERASER_OUTER_BORDER_CIRCLE_NAME = `${CanvasTool.ERASER_NAME_PREFIX}_outer-border-circle`;
|
||||
|
||||
parent: CanvasPreview;
|
||||
manager: CanvasManager;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
@ -47,8 +49,9 @@ export class CanvasTool {
|
||||
*/
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
constructor(parent: CanvasPreview) {
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasTool.GROUP_NAME }),
|
||||
brush: {
|
||||
|
@ -48,13 +48,6 @@ export const bboxReducers = {
|
||||
state.bbox.aspectRatio.id = 'Free';
|
||||
state.bbox.aspectRatio.isLocked = false;
|
||||
}
|
||||
|
||||
if (!state.session.isActive) {
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.bbox.rect.width;
|
||||
state.initialImage.imageObject.height = state.bbox.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
bboxHeightChanged: (
|
||||
state,
|
||||
@ -73,13 +66,6 @@ export const bboxReducers = {
|
||||
state.bbox.aspectRatio.id = 'Free';
|
||||
state.bbox.aspectRatio.isLocked = false;
|
||||
}
|
||||
|
||||
if (!state.session.isActive) {
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.bbox.rect.width;
|
||||
state.initialImage.imageObject.height = state.bbox.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
bboxAspectRatioLockToggled: (state) => {
|
||||
state.bbox.aspectRatio.isLocked = !state.bbox.aspectRatio.isLocked;
|
||||
@ -99,12 +85,6 @@ export const bboxReducers = {
|
||||
state.bbox.rect.width = width;
|
||||
state.bbox.rect.height = height;
|
||||
}
|
||||
if (!state.session.isActive) {
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.bbox.rect.width;
|
||||
state.initialImage.imageObject.height = state.bbox.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
bboxDimensionsSwapped: (state) => {
|
||||
state.bbox.aspectRatio.value = 1 / state.bbox.aspectRatio.value;
|
||||
@ -122,12 +102,6 @@ export const bboxReducers = {
|
||||
state.bbox.rect.height = height;
|
||||
state.bbox.aspectRatio.id = ASPECT_RATIO_MAP[state.bbox.aspectRatio.id].inverseID;
|
||||
}
|
||||
if (!state.session.isActive) {
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.bbox.rect.width;
|
||||
state.initialImage.imageObject.height = state.bbox.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
bboxSizeOptimized: (state) => {
|
||||
const optimalDimension = getOptimalDimension(state.params.model);
|
||||
@ -140,11 +114,5 @@ export const bboxReducers = {
|
||||
state.bbox.rect.width = optimalDimension;
|
||||
state.bbox.rect.height = optimalDimension;
|
||||
}
|
||||
if (!state.session.isActive) {
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.bbox.rect.width;
|
||||
state.initialImage.imageObject.height = state.bbox.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
@ -411,7 +411,6 @@ export const {
|
||||
bboxSizeOptimized,
|
||||
// layers
|
||||
layerAdded,
|
||||
layerAddedFromImage,
|
||||
layerRecalled,
|
||||
layerOpacityChanged,
|
||||
layerAllDeleted,
|
||||
|
@ -4,7 +4,7 @@ import { merge } from 'lodash-es';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type { CanvasImageState, CanvasLayerState, CanvasV2State } from './types';
|
||||
import type { CanvasLayerState, CanvasV2State } from './types';
|
||||
import { imageDTOToImageWithDims } from './types';
|
||||
|
||||
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
||||
@ -16,8 +16,11 @@ export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
||||
|
||||
export const layersReducers = {
|
||||
layerAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; overrides?: Partial<CanvasLayerState> }>) => {
|
||||
const { id } = action.payload;
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; overrides?: Partial<CanvasLayerState>; isSelected?: boolean }>
|
||||
) => {
|
||||
const { id, overrides, isSelected } = action.payload;
|
||||
const layer: CanvasLayerState = {
|
||||
id,
|
||||
type: 'layer',
|
||||
@ -27,12 +30,14 @@ export const layersReducers = {
|
||||
position: { x: 0, y: 0 },
|
||||
imageCache: null,
|
||||
};
|
||||
merge(layer, action.payload.overrides);
|
||||
merge(layer, overrides);
|
||||
state.layers.entities.push(layer);
|
||||
if (isSelected) {
|
||||
state.selectedEntityIdentifier = { type: 'layer', id };
|
||||
}
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
prepare: (payload: { overrides?: Partial<CanvasLayerState> }) => ({
|
||||
prepare: (payload: { overrides?: Partial<CanvasLayerState>; isSelected?: boolean }) => ({
|
||||
payload: { ...payload, id: getPrefixedId('layer') },
|
||||
}),
|
||||
},
|
||||
@ -42,26 +47,6 @@ export const layersReducers = {
|
||||
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerAddedFromImage: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; imageObject: CanvasImageState }>) => {
|
||||
const { id, imageObject } = action.payload;
|
||||
const layer: CanvasLayerState = {
|
||||
id,
|
||||
type: 'layer',
|
||||
isEnabled: true,
|
||||
objects: [imageObject],
|
||||
opacity: 1,
|
||||
position: { x: 0, y: 0 },
|
||||
imageCache: null,
|
||||
};
|
||||
state.layers.entities.push(layer);
|
||||
state.selectedEntityIdentifier = { type: 'layer', id };
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
prepare: (payload: { imageObject: CanvasImageState }) => ({
|
||||
payload: { ...payload, id: getPrefixedId('layer') },
|
||||
}),
|
||||
},
|
||||
layerAllDeleted: (state) => {
|
||||
state.layers.entities = [];
|
||||
state.layers.imageCache = null;
|
||||
|
Loading…
Reference in New Issue
Block a user