mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): inpaint mask rendering
This commit is contained in:
parent
a6a7fe8aba
commit
f4e66bf14f
@ -13,7 +13,7 @@ export const MaskOpacity = memo(() => {
|
||||
const opacity = useAppSelector((s) => Math.round(s.canvasV2.settings.maskOpacity * 100));
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(maskOpacityChanged(v / 100));
|
||||
dispatch(maskOpacityChanged(Math.max(v / 100, 0.25)));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
@ -22,7 +22,7 @@ export const MaskOpacity = memo(() => {
|
||||
<FormLabel m={0}>{t('controlLayers.globalMaskOpacity')}</FormLabel>
|
||||
<Flex gap={4}>
|
||||
<CompositeSlider
|
||||
min={0}
|
||||
min={25}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
@ -32,7 +32,7 @@ export const MaskOpacity = memo(() => {
|
||||
minW={48}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
min={0}
|
||||
min={25}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
|
@ -20,6 +20,7 @@ export class CanvasInpaintMask {
|
||||
getLoggingContext: GetLoggingContext;
|
||||
|
||||
state: CanvasInpaintMaskState;
|
||||
maskOpacity: number;
|
||||
|
||||
transformer: CanvasTransformer;
|
||||
renderer: CanvasObjectRenderer;
|
||||
@ -47,7 +48,7 @@ export class CanvasInpaintMask {
|
||||
};
|
||||
|
||||
this.transformer = new CanvasTransformer(this);
|
||||
this.renderer = new CanvasObjectRenderer(this, true);
|
||||
this.renderer = new CanvasObjectRenderer(this);
|
||||
assert(this.renderer.konva.compositingRect, 'Compositing rect must be set');
|
||||
|
||||
this.konva.layer.add(this.konva.objectGroup);
|
||||
@ -55,6 +56,7 @@ export class CanvasInpaintMask {
|
||||
this.konva.layer.add(...this.transformer.getNodes());
|
||||
|
||||
this.state = state;
|
||||
this.maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
}
|
||||
|
||||
destroy = (): void => {
|
||||
@ -67,14 +69,18 @@ export class CanvasInpaintMask {
|
||||
|
||||
update = async (arg?: { state: CanvasInpaintMaskState; toolState: CanvasV2State['tool']; isSelected: boolean }) => {
|
||||
const state = get(arg, 'state', this.state);
|
||||
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
|
||||
if (!this.isFirstRender && state === this.state) {
|
||||
if (
|
||||
!this.isFirstRender &&
|
||||
state === this.state &&
|
||||
state.fill === this.state.fill &&
|
||||
maskOpacity === this.maskOpacity
|
||||
) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
// const maskOpacity = this.manager.stateApi.getMaskOpacity()
|
||||
|
||||
this.log.debug('Updating');
|
||||
const { position, objects, isEnabled } = state;
|
||||
|
||||
@ -82,18 +88,23 @@ export class CanvasInpaintMask {
|
||||
await this.updateObjects({ objects });
|
||||
}
|
||||
if (this.isFirstRender || position !== this.state.position) {
|
||||
await this.transformer.updatePosition({ position });
|
||||
this.transformer.updatePosition({ position });
|
||||
}
|
||||
// if (this.isFirstRender || opacity !== this.state.opacity) {
|
||||
// await this.updateOpacity({ opacity });
|
||||
// }
|
||||
if (this.isFirstRender || isEnabled !== this.state.isEnabled) {
|
||||
await this.updateVisibility({ isEnabled });
|
||||
this.updateVisibility({ isEnabled });
|
||||
}
|
||||
|
||||
if (this.isFirstRender || state.fill !== this.state.fill || maskOpacity !== this.maskOpacity) {
|
||||
this.renderer.updateCompositingRect(state.fill, maskOpacity);
|
||||
this.maskOpacity = maskOpacity;
|
||||
}
|
||||
// this.transformer.syncInteractionState();
|
||||
|
||||
if (this.isFirstRender) {
|
||||
await this.transformer.updateBbox();
|
||||
this.transformer.updateBbox();
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
@ -110,8 +121,6 @@ export class CanvasInpaintMask {
|
||||
if (didUpdate) {
|
||||
this.transformer.requestRectCalculation();
|
||||
}
|
||||
|
||||
this.isFirstRender = false;
|
||||
};
|
||||
|
||||
// updateOpacity = (arg?: { opacity: number }) => {
|
||||
|
@ -366,6 +366,7 @@ export class CanvasManager {
|
||||
let currentFill: RgbaColor = state.tool.fill;
|
||||
const selectedEntity = this.getSelectedEntity();
|
||||
if (selectedEntity) {
|
||||
// These two entity types use a compositing rect for opacity. Their alpha is always 1.
|
||||
if (selectedEntity.state.type === 'regional_guidance') {
|
||||
currentFill = { ...selectedEntity.state.fill, a: 1 };
|
||||
} else if (selectedEntity.state.type === 'inpaint_mask') {
|
||||
@ -470,6 +471,7 @@ export class CanvasManager {
|
||||
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.inpaintMask !== this._prevState.inpaintMask ||
|
||||
state.settings.maskOpacity !== this._prevState.settings.maskOpacity ||
|
||||
state.tool.selected !== this._prevState.tool.selected ||
|
||||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
||||
|
@ -14,6 +14,7 @@ import type {
|
||||
CanvasEraserLineState,
|
||||
CanvasImageState,
|
||||
CanvasRectState,
|
||||
RgbColor,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
@ -58,11 +59,26 @@ export class CanvasObjectRenderer {
|
||||
*/
|
||||
renderers: Map<string, AnyObjectRenderer> = new Map();
|
||||
|
||||
/**
|
||||
* A object containing singleton Konva nodes.
|
||||
*/
|
||||
konva: {
|
||||
/**
|
||||
* The compositing rect is used to draw the inpaint mask as a single shape with a given opacity.
|
||||
*
|
||||
* When drawing multiple transparent shapes on a canvas, overlapping regions will be more opaque. This doesn't
|
||||
* match the expectation for a mask, where all shapes should have the same opacity, even if they overlap.
|
||||
*
|
||||
* To prevent this, we use a trick. Instead of drawing all shapes at the desired opacity, we draw them at opacity of 1.
|
||||
* Then we draw a single rect that covers the entire canvas at the desired opacity, with a globalCompositeOperation
|
||||
* of 'source-in'. The shapes effectively become a mask for the "compositing rect".
|
||||
*
|
||||
* This node is only added when the parent of the renderer is an inpaint mask or region, which require this behavior.
|
||||
*/
|
||||
compositingRect: Konva.Rect | null;
|
||||
};
|
||||
|
||||
constructor(parent: CanvasLayer | CanvasInpaintMask, withCompositingRect: boolean = false) {
|
||||
constructor(parent: CanvasLayer | CanvasInpaintMask) {
|
||||
this.id = getPrefixedId(CanvasObjectRenderer.TYPE);
|
||||
this.parent = parent;
|
||||
this.manager = parent.manager;
|
||||
@ -74,10 +90,11 @@ export class CanvasObjectRenderer {
|
||||
compositingRect: null,
|
||||
};
|
||||
|
||||
if (withCompositingRect) {
|
||||
if (this.parent.type === 'inpaint_mask') {
|
||||
this.konva.compositingRect = new Konva.Rect({
|
||||
name: CanvasObjectRenderer.KONVA_COMPOSITING_RECT_NAME,
|
||||
listening: false,
|
||||
globalCompositeOperation: 'source-in',
|
||||
});
|
||||
this.parent.konva.objectGroup.add(this.konva.compositingRect);
|
||||
}
|
||||
@ -131,27 +148,20 @@ export class CanvasObjectRenderer {
|
||||
didRender = (await this.renderObject(this.buffer)) || didRender;
|
||||
}
|
||||
|
||||
if (didRender && this.parent.type === 'inpaint_mask') {
|
||||
assert(this.konva.compositingRect, 'Compositing rect must exist for inpaint mask');
|
||||
|
||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||
const rgbColor = rgbColorToString(this.parent.state.fill);
|
||||
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
|
||||
this.konva.compositingRect.setAttrs({
|
||||
fill: rgbColor,
|
||||
opacity: maskOpacity,
|
||||
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||
globalCompositeOperation: 'source-in',
|
||||
visible: true,
|
||||
// This rect must always be on top of all other shapes
|
||||
// zIndex: this.renderers.size + 1,
|
||||
});
|
||||
}
|
||||
|
||||
return didRender;
|
||||
};
|
||||
|
||||
updateCompositingRect = (fill: RgbColor, opacity: number) => {
|
||||
this.log.trace('Updating compositing rect');
|
||||
assert(this.konva.compositingRect, 'Missing compositing rect');
|
||||
|
||||
const rgbColor = rgbColorToString(fill);
|
||||
this.konva.compositingRect.setAttrs({
|
||||
fill: rgbColor,
|
||||
opacity,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the given object. If the object renderer does not exist, it will be created and its Konva group added to the
|
||||
* parent entity's object group.
|
||||
@ -230,12 +240,6 @@ export class CanvasObjectRenderer {
|
||||
|
||||
this.buffer = objectState;
|
||||
return await this.renderObject(this.buffer, true);
|
||||
|
||||
// const didDraw = await this.renderObject(this.buffer, true);
|
||||
// if (didDraw && this.konva.compositingRect) {
|
||||
// this.konva.compositingRect.zIndex(this.renderers.size + 1);
|
||||
// }
|
||||
// return didDraw;
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user