mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-02 07:25:07 -05:00
feat(ui): abstract out CanvasEntityAdapterBase
Things were getting to complex to reason about & classes a bit complicated. Trying to simplify...
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
selectEntity,
|
||||
selectSelectedEntityIdentifier,
|
||||
} from 'features/controlLayers/store/selectors';
|
||||
import { isDrawableEntity } from 'features/controlLayers/store/types';
|
||||
import { isRenderableEntity } from 'features/controlLayers/store/types';
|
||||
import { clamp, round } from 'lodash-es';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
@@ -70,7 +70,7 @@ const selectOpacity = createSelector(selectCanvasSlice, (canvas) => {
|
||||
if (!selectedEntity) {
|
||||
return 1; // fallback to 100% opacity
|
||||
}
|
||||
if (!isDrawableEntity(selectedEntity)) {
|
||||
if (!isRenderableEntity(selectedEntity)) {
|
||||
return 1; // fallback to 100% opacity
|
||||
}
|
||||
// Opacity is a float from 0-1, but we want to display it as a percentage
|
||||
|
||||
@@ -1,86 +1,73 @@
|
||||
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import type { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsCounterClockwiseBold, PiArrowsOutBold, PiCheckBold, PiXBold } from 'react-icons/pi';
|
||||
|
||||
const TransformBox = memo(
|
||||
({
|
||||
adapter,
|
||||
}: {
|
||||
adapter:
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const isProcessing = useStore(adapter.transformer.$isProcessing);
|
||||
const TransformBox = memo(({ adapter }: { adapter: CanvasEntityAdapterBase }) => {
|
||||
const { t } = useTranslation();
|
||||
const isProcessing = useStore(adapter.transformer.$isProcessing);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
bg="base.800"
|
||||
borderRadius="base"
|
||||
p={4}
|
||||
flexDir="column"
|
||||
gap={4}
|
||||
w={420}
|
||||
h="auto"
|
||||
shadow="dark-lg"
|
||||
transitionProperty="height"
|
||||
transitionDuration="normal"
|
||||
>
|
||||
<Heading size="md" color="base.300" userSelect="none">
|
||||
{t('controlLayers.transform.transform')}
|
||||
</Heading>
|
||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||
<Button
|
||||
leftIcon={<PiArrowsOutBold />}
|
||||
onClick={adapter.transformer.fitProxyRectToBbox}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.transform.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.fitToBbox')}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.transformer.resetTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.reset')}
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<PiCheckBold />}
|
||||
onClick={adapter.transformer.applyTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('common.apply')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.apply')}
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.transformer.stopTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('common.cancel')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.cancel')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
bg="base.800"
|
||||
borderRadius="base"
|
||||
p={4}
|
||||
flexDir="column"
|
||||
gap={4}
|
||||
w={420}
|
||||
h="auto"
|
||||
shadow="dark-lg"
|
||||
transitionProperty="height"
|
||||
transitionDuration="normal"
|
||||
>
|
||||
<Heading size="md" color="base.300" userSelect="none">
|
||||
{t('controlLayers.transform.transform')}
|
||||
</Heading>
|
||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||
<Button
|
||||
leftIcon={<PiArrowsOutBold />}
|
||||
onClick={adapter.transformer.fitProxyRectToBbox}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.transform.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.fitToBbox')}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.transformer.resetTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.reset')}
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<PiCheckBold />}
|
||||
onClick={adapter.transformer.applyTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('common.apply')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.apply')}
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.transformer.stopTransform}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('common.cancel')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.cancel')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
TransformBox.displayName = 'Transform';
|
||||
|
||||
|
||||
@@ -1,73 +1,17 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasControlLayerState, CanvasEntityIdentifier, Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasControlLayerAdapter extends CanvasModuleBase {
|
||||
readonly type = 'control_layer_adapter';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly manager: CanvasManager;
|
||||
readonly parent: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
entityIdentifier: CanvasEntityIdentifier<'control_layer'>;
|
||||
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
export class CanvasControlLayerAdapter extends CanvasEntityAdapterBase<CanvasControlLayerState> {
|
||||
static TYPE = 'control_layer_adapter';
|
||||
private _state: CanvasControlLayerState | null = null;
|
||||
|
||||
/**
|
||||
* The Konva nodes that make up the entity layer:
|
||||
* - A layer to hold the everything
|
||||
*
|
||||
* Note that the transformer and object renderer have their own Konva nodes, but they are not stored here.
|
||||
*/
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
/**
|
||||
* The transformer for this entity layer.
|
||||
*/
|
||||
transformer: CanvasEntityTransformer;
|
||||
|
||||
/**
|
||||
* The renderer for this entity layer.
|
||||
*/
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'control_layer'>, manager: CanvasManager) {
|
||||
super();
|
||||
this.id = entityIdentifier.id;
|
||||
this.entityIdentifier = entityIdentifier;
|
||||
this.manager = manager;
|
||||
this.parent = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
super(entityIdentifier, manager, CanvasControlLayerAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasControlLayerState {
|
||||
@@ -134,34 +78,4 @@ export class CanvasControlLayerAdapter extends CanvasModuleBase {
|
||||
const keysToOmit: (keyof CanvasControlLayerState)[] = ['name', 'controlAdapter', 'withTransparencyEffect'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying module');
|
||||
this.renderer.destroy();
|
||||
this.transformer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
transformer: this.transformer.repr(),
|
||||
renderer: this.renderer.repr(),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasEntityIdentifier, CanvasRenderableEntityState, Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
|
||||
export abstract class CanvasEntityAdapterBase<
|
||||
T extends CanvasRenderableEntityState = CanvasRenderableEntityState,
|
||||
> extends CanvasModuleBase {
|
||||
readonly type: string;
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly manager: CanvasManager;
|
||||
readonly parent: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
readonly entityIdentifier: CanvasEntityIdentifier<T['type']>;
|
||||
|
||||
/**
|
||||
* The Konva nodes that make up the entity adapter:
|
||||
* - A Konva.Layer to hold the everything
|
||||
*
|
||||
* Note that the transformer and object renderer have their own Konva nodes, but they are not stored here.
|
||||
*/
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
/**
|
||||
* The transformer for this entity adapter.
|
||||
*/
|
||||
transformer: CanvasEntityTransformer;
|
||||
|
||||
/**
|
||||
* The renderer for this entity adapter.
|
||||
*/
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<T['type']>, manager: CanvasManager, adapterType: string) {
|
||||
super();
|
||||
this.type = adapterType;
|
||||
this.id = entityIdentifier.id;
|
||||
this.entityIdentifier = entityIdentifier;
|
||||
this.manager = manager;
|
||||
this.parent = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
}
|
||||
|
||||
abstract get state(): T;
|
||||
|
||||
abstract set state(state: T);
|
||||
|
||||
abstract getCanvas: (rect?: Rect) => HTMLCanvasElement;
|
||||
|
||||
abstract getHashableState: () => SerializableObject;
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying module');
|
||||
this.renderer.destroy();
|
||||
this.transformer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
transformer: this.transformer.repr(),
|
||||
renderer: this.renderer.repr(),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { getLastPointOfLine } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasControlLayerState,
|
||||
CanvasEntityIdentifier,
|
||||
CanvasEraserLineState,
|
||||
CanvasRasterLayerState,
|
||||
Coordinate,
|
||||
Rect,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { get, omit } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
/**
|
||||
* Handles the rendering for a single raster or control layer entity.
|
||||
*
|
||||
* This module has two main components:
|
||||
* - A transformer, which handles the positioning and interaction state of the layer
|
||||
* - A renderer, which handles the rendering of the layer's objects
|
||||
*
|
||||
* The canvas rendering module interacts with this module to coordinate the rendering of all raster and control layers.
|
||||
*/
|
||||
export class CanvasEntityLayerAdapter extends CanvasModuleBase {
|
||||
readonly type = 'entity_layer_adapter';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly manager: CanvasManager;
|
||||
readonly parent: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
state: CanvasRasterLayerState | CanvasControlLayerState;
|
||||
|
||||
/**
|
||||
* The Konva nodes that make up the entity layer:
|
||||
* - A layer to hold the everything
|
||||
*
|
||||
* Note that the transformer and object renderer have their own Konva nodes, but they are not stored here.
|
||||
*/
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
/**
|
||||
* The transformer for this entity layer.
|
||||
*/
|
||||
transformer: CanvasEntityTransformer;
|
||||
|
||||
/**
|
||||
* The renderer for this entity layer.
|
||||
*/
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
/**
|
||||
* Whether this is the first render of the entity layer.
|
||||
*/
|
||||
isFirstRender: boolean = true;
|
||||
|
||||
constructor(state: CanvasEntityLayerAdapter['state'], manager: CanvasEntityLayerAdapter['manager']) {
|
||||
super();
|
||||
this.id = state.id;
|
||||
this.manager = manager;
|
||||
this.parent = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.state = state;
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this entity's entity identifier
|
||||
*/
|
||||
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||
return getEntityIdentifier(this.state);
|
||||
};
|
||||
|
||||
update = async (arg?: { state: CanvasEntityLayerAdapter['state'] }): Promise<void> => {
|
||||
const state = get(arg, 'state', this.state);
|
||||
|
||||
const prevState = this.state;
|
||||
this.state = state;
|
||||
|
||||
if (!this.isFirstRender && prevState === state) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.debug('Updating');
|
||||
const { position, objects, opacity, isEnabled, isLocked } = state;
|
||||
|
||||
if (this.isFirstRender || isEnabled !== prevState.isEnabled) {
|
||||
this.updateVisibility({ isEnabled });
|
||||
}
|
||||
if (this.isFirstRender || isLocked !== prevState.isLocked) {
|
||||
this.transformer.syncInteractionState();
|
||||
}
|
||||
if (this.isFirstRender || objects !== prevState.objects) {
|
||||
await this.updateObjects({ objects });
|
||||
}
|
||||
if (this.isFirstRender || position !== prevState.position) {
|
||||
this.transformer.updatePosition({ position });
|
||||
}
|
||||
if (this.isFirstRender || opacity !== prevState.opacity) {
|
||||
this.renderer.updateOpacity(opacity);
|
||||
}
|
||||
|
||||
if (state.type === 'control_layer' && prevState.type === 'control_layer') {
|
||||
if (this.isFirstRender || state.withTransparencyEffect !== prevState.withTransparencyEffect) {
|
||||
this.renderer.updateTransparencyEffect(state.withTransparencyEffect);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isFirstRender) {
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
|
||||
this.isFirstRender = false;
|
||||
};
|
||||
|
||||
updateVisibility = (arg?: { isEnabled: boolean }) => {
|
||||
this.log.trace('Updating visibility');
|
||||
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
|
||||
this.konva.layer.visible(isEnabled);
|
||||
this.renderer.syncCache(isEnabled);
|
||||
};
|
||||
|
||||
updateObjects = async (arg?: { objects: CanvasRasterLayerState['objects'] }) => {
|
||||
this.log.trace('Updating objects');
|
||||
|
||||
const objects = get(arg, 'objects', this.state.objects);
|
||||
|
||||
const didUpdate = await this.renderer.render(objects);
|
||||
|
||||
if (didUpdate) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
this.log.trace({ rect }, 'Getting canvas');
|
||||
// The opacity may have been changed in response to user selecting a different entity category, so we must restore
|
||||
// the original opacity before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: this.state.opacity };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
if (this.state.type === 'control_layer') {
|
||||
const keysToOmit: (keyof CanvasControlLayerState)[] = ['name', 'controlAdapter', 'withTransparencyEffect'];
|
||||
return omit(this.state, keysToOmit);
|
||||
} else if (this.state.type === 'raster_layer') {
|
||||
const keysToOmit: (keyof CanvasRasterLayerState)[] = ['name'];
|
||||
return omit(this.state, keysToOmit);
|
||||
} else {
|
||||
assert(false, 'Unexpected layer type');
|
||||
}
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
getLastPointOfLastLine = (type: CanvasBrushLineState['type'] | CanvasEraserLineState['type']): Coordinate | null => {
|
||||
const lastObject = this.state.objects[this.state.objects.length - 1];
|
||||
if (!lastObject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lastObject.type === type) {
|
||||
return getLastPointOfLine(lastObject.points);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying module');
|
||||
this.renderer.destroy();
|
||||
this.transformer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
transformer: this.transformer.repr(),
|
||||
renderer: this.renderer.repr(),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { getLastPointOfLine } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEntityIdentifier,
|
||||
CanvasEraserLineState,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRegionalGuidanceState,
|
||||
Coordinate,
|
||||
Rect,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { get, omit } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
|
||||
export class CanvasEntityMaskAdapter extends CanvasModuleBase {
|
||||
readonly type = 'entity_mask_adapter';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly parent: CanvasManager;
|
||||
readonly manager: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
state: CanvasInpaintMaskState | CanvasRegionalGuidanceState;
|
||||
|
||||
transformer: CanvasEntityTransformer;
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
isFirstRender: boolean = true;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
constructor(state: CanvasEntityMaskAdapter['state'], manager: CanvasEntityMaskAdapter['manager']) {
|
||||
super();
|
||||
this.id = state.id;
|
||||
this.parent = manager;
|
||||
this.manager = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.state = state;
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this entity's entity identifier
|
||||
*/
|
||||
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||
return getEntityIdentifier(this.state);
|
||||
};
|
||||
|
||||
update = async (arg?: { state: CanvasEntityMaskAdapter['state'] }) => {
|
||||
const state = get(arg, 'state', this.state);
|
||||
|
||||
const prevState = this.state;
|
||||
this.state = state;
|
||||
|
||||
if (!this.isFirstRender && prevState === state && prevState.fill === state.fill) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.debug('Updating');
|
||||
const { position, objects, isEnabled, isLocked, opacity } = state;
|
||||
|
||||
if (this.isFirstRender || objects !== prevState.objects) {
|
||||
await this.updateObjects({ objects });
|
||||
}
|
||||
if (this.isFirstRender || position !== prevState.position) {
|
||||
this.transformer.updatePosition({ position });
|
||||
}
|
||||
if (this.isFirstRender || opacity !== prevState.opacity) {
|
||||
this.renderer.updateOpacity(opacity);
|
||||
}
|
||||
if (this.isFirstRender || isEnabled !== prevState.isEnabled) {
|
||||
this.updateVisibility({ isEnabled });
|
||||
}
|
||||
if (this.isFirstRender || isLocked !== prevState.isLocked) {
|
||||
this.transformer.syncInteractionState();
|
||||
}
|
||||
if (this.isFirstRender || state.fill !== prevState.fill) {
|
||||
this.renderer.updateCompositingRectFill(state.fill);
|
||||
}
|
||||
|
||||
if (this.isFirstRender) {
|
||||
this.renderer.updateCompositingRectSize();
|
||||
}
|
||||
|
||||
if (this.isFirstRender) {
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
|
||||
this.isFirstRender = false;
|
||||
};
|
||||
|
||||
updateObjects = async (arg?: { objects: CanvasInpaintMaskState['objects'] }) => {
|
||||
this.log.trace('Updating objects');
|
||||
|
||||
const objects = get(arg, 'objects', this.state.objects);
|
||||
|
||||
const didUpdate = await this.renderer.render(objects);
|
||||
|
||||
if (didUpdate) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
};
|
||||
|
||||
updateVisibility = (arg?: { isEnabled: boolean }) => {
|
||||
this.log.trace('Updating visibility');
|
||||
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
|
||||
this.konva.layer.visible(isEnabled);
|
||||
};
|
||||
|
||||
getLastPointOfLastLine = (type: CanvasBrushLineState['type'] | CanvasEraserLineState['type']): Coordinate | null => {
|
||||
const lastObject = this.state.objects[this.state.objects.length - 1];
|
||||
if (!lastObject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lastObject.type === type) {
|
||||
return getLastPointOfLine(lastObject.points);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasEntityMaskAdapter['state'])[] = ['fill', 'name', 'opacity'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
const attrs: GroupConfig = { opacity: 1 };
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.transformer.destroy();
|
||||
this.renderer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { CanvasObjectBrushLineRenderer } from 'features/controlLayers/konva/CanvasObjectBrushLineRenderer';
|
||||
import { CanvasObjectEraserLineRenderer } from 'features/controlLayers/konva/CanvasObjectEraserLineRenderer';
|
||||
import { CanvasObjectImageRenderer } from 'features/controlLayers/konva/CanvasObjectImageRenderer';
|
||||
import { CanvasObjectRectRenderer } from 'features/controlLayers/konva/CanvasObjectRectRenderer';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
|
||||
import { getPatternSVG } from 'features/controlLayers/konva/patterns/getPatternSVG';
|
||||
import {
|
||||
@@ -66,11 +63,7 @@ export class CanvasEntityRenderer extends CanvasModuleBase {
|
||||
readonly type = 'entity_renderer';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly parent:
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter;
|
||||
readonly parent: CanvasEntityAdapterBase;
|
||||
readonly manager: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
@@ -141,13 +134,7 @@ export class CanvasEntityRenderer extends CanvasModuleBase {
|
||||
*/
|
||||
$canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null);
|
||||
|
||||
constructor(
|
||||
parent:
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter
|
||||
) {
|
||||
constructor(parent: CanvasEntityAdapterBase) {
|
||||
super();
|
||||
this.id = getPrefixedId(this.type);
|
||||
this.parent = parent;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasRasterLayerAdapter } from 'features/controlLayers/konva/CanvasRasterLayerAdapter';
|
||||
import type { CanvasRegionalGuidanceAdapter } from 'features/controlLayers/konva/CanvasRegionalGuidanceAdapter';
|
||||
import { canvasToImageData, getEmptyRect, getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { Coordinate, Rect, RectWithRotation } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
@@ -82,11 +79,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
|
||||
readonly type = 'entity_transformer';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly parent:
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter;
|
||||
readonly parent: CanvasEntityAdapterBase;
|
||||
readonly manager: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
|
||||
@@ -1,61 +1,21 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasEntityIdentifier, CanvasInpaintMaskState, Rect } from 'features/controlLayers/store/types';
|
||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasInpaintMaskAdapter extends CanvasModuleBase {
|
||||
readonly type = 'inpaint_mask_adapter';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly parent: CanvasManager;
|
||||
readonly manager: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
entityIdentifier: CanvasEntityIdentifier<'inpaint_mask'>;
|
||||
export class CanvasInpaintMaskAdapter extends CanvasEntityAdapterBase<CanvasInpaintMaskState> {
|
||||
static TYPE = 'inpaint_mask_adapter';
|
||||
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
private _state: CanvasInpaintMaskState | null = null;
|
||||
|
||||
transformer: CanvasEntityTransformer;
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'inpaint_mask'>, manager: CanvasManager) {
|
||||
super();
|
||||
this.id = entityIdentifier.id;
|
||||
this.entityIdentifier = entityIdentifier;
|
||||
this.parent = manager;
|
||||
this.manager = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
super(entityIdentifier, manager, CanvasInpaintMaskAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasInpaintMaskState {
|
||||
@@ -71,13 +31,6 @@ export class CanvasInpaintMaskAdapter extends CanvasModuleBase {
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this entity's entity identifier
|
||||
*/
|
||||
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||
return getEntityIdentifier(this.state);
|
||||
};
|
||||
|
||||
update = async (state: CanvasInpaintMaskState) => {
|
||||
const prevState = this.state;
|
||||
this.state = state;
|
||||
@@ -125,14 +78,6 @@ export class CanvasInpaintMaskAdapter extends CanvasModuleBase {
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
@@ -140,24 +85,4 @@ export class CanvasInpaintMaskAdapter extends CanvasModuleBase {
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.transformer.destroy();
|
||||
this.renderer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,73 +1,20 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasEntityIdentifier, CanvasRasterLayerState, Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasRasterLayerAdapter extends CanvasModuleBase {
|
||||
readonly type = 'raster_layer_adapter';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly manager: CanvasManager;
|
||||
readonly parent: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
entityIdentifier: CanvasEntityIdentifier<'raster_layer'>;
|
||||
|
||||
export class CanvasRasterLayerAdapter extends CanvasEntityAdapterBase<CanvasRasterLayerState> {
|
||||
static TYPE = 'raster_layer_adapter';
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
private _state: CanvasRasterLayerState | null = null;
|
||||
|
||||
/**
|
||||
* The Konva nodes that make up the entity layer:
|
||||
* - A layer to hold the everything
|
||||
*
|
||||
* Note that the transformer and object renderer have their own Konva nodes, but they are not stored here.
|
||||
*/
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
/**
|
||||
* The transformer for this entity layer.
|
||||
*/
|
||||
transformer: CanvasEntityTransformer;
|
||||
|
||||
/**
|
||||
* The renderer for this entity layer.
|
||||
*/
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'raster_layer'>, manager: CanvasManager) {
|
||||
super();
|
||||
this.id = entityIdentifier.id;
|
||||
this.entityIdentifier = entityIdentifier;
|
||||
this.manager = manager;
|
||||
this.parent = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
super(entityIdentifier, manager, CanvasRasterLayerAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasRasterLayerState {
|
||||
@@ -126,38 +73,8 @@ export class CanvasRasterLayerAdapter extends CanvasModuleBase {
|
||||
return canvas;
|
||||
};
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
getHashableState = (): SerializableObject => {
|
||||
const keysToOmit: (keyof CanvasRasterLayerState)[] = ['name'];
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying module');
|
||||
this.renderer.destroy();
|
||||
this.transformer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
transformer: this.transformer.repr(),
|
||||
renderer: this.renderer.repr(),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,61 +1,21 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasEntityRenderer } from 'features/controlLayers/konva/CanvasEntityRenderer';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer';
|
||||
import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import type { CanvasEntityIdentifier, CanvasRegionalGuidanceState, Rect } from 'features/controlLayers/store/types';
|
||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { omit } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import stableHash from 'stable-hash';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasRegionalGuidanceAdapter extends CanvasModuleBase {
|
||||
readonly type = 'regional_guidance_adapter';
|
||||
readonly id: string;
|
||||
readonly path: string[];
|
||||
readonly parent: CanvasManager;
|
||||
readonly manager: CanvasManager;
|
||||
readonly log: Logger;
|
||||
|
||||
entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>;
|
||||
export class CanvasRegionalGuidanceAdapter extends CanvasEntityAdapterBase<CanvasRegionalGuidanceState> {
|
||||
static TYPE = 'regional_guidance_adapter';
|
||||
|
||||
/**
|
||||
* The last known state of the entity.
|
||||
*/
|
||||
private _state: CanvasRegionalGuidanceState | null = null;
|
||||
|
||||
transformer: CanvasEntityTransformer;
|
||||
renderer: CanvasEntityRenderer;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, manager: CanvasManager) {
|
||||
super();
|
||||
this.id = entityIdentifier.id;
|
||||
this.entityIdentifier = entityIdentifier;
|
||||
this.parent = manager;
|
||||
this.manager = manager;
|
||||
this.path = this.manager.buildPath(this);
|
||||
this.log = this.manager.buildLogger(this);
|
||||
|
||||
this.log.debug('Creating module');
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: `${this.type}:layer`,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
};
|
||||
|
||||
this.renderer = new CanvasEntityRenderer(this);
|
||||
this.transformer = new CanvasEntityTransformer(this);
|
||||
super(entityIdentifier, manager, CanvasRegionalGuidanceAdapter.TYPE);
|
||||
}
|
||||
|
||||
get state(): CanvasRegionalGuidanceState {
|
||||
@@ -68,20 +28,12 @@ export class CanvasRegionalGuidanceAdapter extends CanvasModuleBase {
|
||||
}
|
||||
|
||||
set state(state: CanvasRegionalGuidanceState) {
|
||||
const prevState = this._state;
|
||||
this._state = state;
|
||||
this.render(state, prevState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this entity's entity identifier
|
||||
*/
|
||||
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||
return getEntityIdentifier(this.state);
|
||||
};
|
||||
|
||||
update = async (state: CanvasRegionalGuidanceState) => {
|
||||
const prevState = this.state;
|
||||
this.state = state;
|
||||
|
||||
render = async (state: CanvasRegionalGuidanceState, prevState: CanvasRegionalGuidanceState | null) => {
|
||||
if (prevState && prevState === state) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
@@ -125,14 +77,6 @@ export class CanvasRegionalGuidanceAdapter extends CanvasModuleBase {
|
||||
return omit(this.state, keysToOmit);
|
||||
};
|
||||
|
||||
hash = (extra?: SerializableObject): string => {
|
||||
const arg = {
|
||||
state: this.getHashableState(),
|
||||
extra,
|
||||
};
|
||||
return stableHash(arg);
|
||||
};
|
||||
|
||||
getCanvas = (rect?: Rect): HTMLCanvasElement => {
|
||||
// The opacity may have been changed in response to user selecting a different entity category, and the mask regions
|
||||
// should be fully opaque - set opacity to 1 before rendering the canvas
|
||||
@@ -140,24 +84,4 @@ export class CanvasRegionalGuidanceAdapter extends CanvasModuleBase {
|
||||
const canvas = this.renderer.getCanvas(rect, attrs);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
isInteractable = (): boolean => {
|
||||
return this.state.isEnabled && !this.state.isLocked;
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying module');
|
||||
this.transformer.destroy();
|
||||
this.renderer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { $alt, $ctrl, $meta, $shift } from '@invoke-ai/ui-library';
|
||||
import type { AppStore } from 'app/store/store';
|
||||
import type { CanvasControlLayerAdapter } from 'features/controlLayers/konva/CanvasControlLayerAdapter';
|
||||
import type { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapterBase';
|
||||
import type { CanvasInpaintMaskAdapter } from 'features/controlLayers/konva/CanvasInpaintMaskAdapter';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
@@ -362,13 +363,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
||||
/**
|
||||
* The entity adapter being transformed, if any.
|
||||
*/
|
||||
$transformingAdapter = atom<
|
||||
| CanvasRasterLayerAdapter
|
||||
| CanvasControlLayerAdapter
|
||||
| CanvasInpaintMaskAdapter
|
||||
| CanvasRegionalGuidanceAdapter
|
||||
| null
|
||||
>(null);
|
||||
$transformingAdapter = atom<CanvasEntityAdapterBase | null>(null);
|
||||
|
||||
/**
|
||||
* Whether an entity is currently being transformed. Derived from `$transformingAdapter`.
|
||||
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
RgbColor,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isDrawableEntity, RGBA_BLACK } from 'features/controlLayers/store/types';
|
||||
import { isRenderableEntity, RGBA_BLACK } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { atom } from 'nanostores';
|
||||
@@ -171,7 +171,7 @@ export class CanvasToolModule extends CanvasModuleBase {
|
||||
!!selectedEntity &&
|
||||
selectedEntity.state.isEnabled &&
|
||||
!selectedEntity.state.isLocked &&
|
||||
isDrawableEntity(selectedEntity.state);
|
||||
isRenderableEntity(selectedEntity.state);
|
||||
|
||||
this.syncCursorStyle();
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ import {
|
||||
imageDTOToImageWithDims,
|
||||
initialControlNet,
|
||||
initialIPAdapter,
|
||||
isDrawableEntity,
|
||||
isRenderableEntity,
|
||||
} from './types';
|
||||
|
||||
const DEFAULT_MASK_COLORS: RgbColor[] = [
|
||||
@@ -818,7 +818,7 @@ export const canvasSlice = createSlice({
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (isDrawableEntity(entity)) {
|
||||
} else if (isRenderableEntity(entity)) {
|
||||
entity.isEnabled = true;
|
||||
entity.objects = [];
|
||||
entity.position = { x: 0, y: 0 };
|
||||
@@ -907,7 +907,7 @@ export const canvasSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDrawableEntity(entity)) {
|
||||
if (isRenderableEntity(entity)) {
|
||||
entity.position = position;
|
||||
}
|
||||
},
|
||||
@@ -918,7 +918,7 @@ export const canvasSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDrawableEntity(entity)) {
|
||||
if (isRenderableEntity(entity)) {
|
||||
if (replaceObjects) {
|
||||
entity.objects = [imageObject];
|
||||
entity.position = { x: rect.x, y: rect.y };
|
||||
@@ -932,7 +932,7 @@ export const canvasSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDrawableEntity(entity)) {
|
||||
if (!isRenderableEntity(entity)) {
|
||||
assert(false, `Cannot add a brush line to a non-drawable entity of type ${entity.type}`);
|
||||
}
|
||||
|
||||
@@ -947,7 +947,7 @@ export const canvasSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDrawableEntity(entity)) {
|
||||
if (!isRenderableEntity(entity)) {
|
||||
assert(false, `Cannot add a eraser line to a non-drawable entity of type ${entity.type}`);
|
||||
}
|
||||
|
||||
@@ -962,7 +962,7 @@ export const canvasSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDrawableEntity(entity)) {
|
||||
if (!isRenderableEntity(entity)) {
|
||||
assert(false, `Cannot add a rect to a non-drawable entity of type ${entity.type}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -675,6 +675,12 @@ export type CanvasEntityState =
|
||||
| CanvasInpaintMaskState
|
||||
| CanvasIPAdapterState;
|
||||
|
||||
export type CanvasRenderableEntityState =
|
||||
| CanvasRasterLayerState
|
||||
| CanvasControlLayerState
|
||||
| CanvasRegionalGuidanceState
|
||||
| CanvasInpaintMaskState;
|
||||
|
||||
export type CanvasEntityType = CanvasEntityState['type'];
|
||||
export type CanvasEntityIdentifier<T extends CanvasEntityType = CanvasEntityType> = { id: string; type: T };
|
||||
|
||||
@@ -773,7 +779,9 @@ export type EntityRasterizedPayload = EntityIdentifierPayload<{
|
||||
|
||||
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';
|
||||
|
||||
export function isDrawableEntityType(entityType: CanvasEntityState['type']) {
|
||||
export function isDrawableEntityType(
|
||||
entityType: CanvasEntityState['type']
|
||||
): entityType is CanvasRenderableEntityState['type'] {
|
||||
return (
|
||||
entityType === 'raster_layer' ||
|
||||
entityType === 'control_layer' ||
|
||||
@@ -782,9 +790,7 @@ export function isDrawableEntityType(entityType: CanvasEntityState['type']) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isDrawableEntity(
|
||||
entity: CanvasEntityState
|
||||
): entity is CanvasRasterLayerState | CanvasControlLayerState | CanvasRegionalGuidanceState | CanvasInpaintMaskState {
|
||||
export function isRenderableEntity(entity: CanvasEntityState): entity is CanvasRenderableEntityState {
|
||||
return isDrawableEntityType(entity.type);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user