feat(ui): wip canvas nodes migration 2

This commit is contained in:
psychedelicious 2023-05-03 08:55:36 +10:00
parent 08ec12b391
commit cee21ca082
7 changed files with 220 additions and 66 deletions

View File

@ -1,18 +1,3 @@
export const getIsImageDataPartiallyTransparent = (imageData: ImageData) => {
let hasTransparency = false;
let isFullyTransparent = true;
const len = imageData.data.length;
let i = 3;
for (i; i < len; i += 4) {
if (imageData.data[i] !== 0) {
isFullyTransparent = false;
} else {
hasTransparency = true;
}
}
return { hasTransparency, isFullyTransparent };
};
export const getImageDataTransparency = (imageData: ImageData) => { export const getImageDataTransparency = (imageData: ImageData) => {
let isFullyTransparent = true; let isFullyTransparent = true;
let isPartiallyTransparent = false; let isPartiallyTransparent = false;
@ -46,14 +31,3 @@ export const areAnyPixelsBlack = (imageData: ImageData) => {
} }
return false; return false;
}; };
export const getIsImageDataWhite = (imageData: ImageData) => {
const len = imageData.data.length;
let i = 0;
for (i; i < len; ) {
if (imageData.data[i++] !== 255) {
return false;
}
}
return true;
};

View File

@ -0,0 +1,30 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from '../store/canvasSelectors';
import { useMemo } from 'react';
import { getCanvasNodeType } from '../util/getCanvasNodeType';
const selector = createSelector(canvasSelector, (canvas) => {
const {
layerState: { objects },
boundingBoxCoordinates,
boundingBoxDimensions,
stageScale,
isMaskEnabled,
} = canvas;
return {
objects,
boundingBoxCoordinates,
boundingBoxDimensions,
stageScale,
isMaskEnabled,
};
});
export const useGetCanvasNodeType = () => {
const data = useAppSelector(selector);
const nodeType = useMemo(() => getCanvasNodeType(data), [data]);
return nodeType;
};

View File

@ -1,5 +1,6 @@
import { CanvasMaskLine } from 'features/canvas/store/canvasTypes'; import { CanvasMaskLine } from 'features/canvas/store/canvasTypes';
import Konva from 'konva'; import Konva from 'konva';
import { Stage } from 'konva/lib/Stage';
import { IRect } from 'konva/lib/types'; import { IRect } from 'konva/lib/types';
/** /**
@ -12,10 +13,50 @@ import { IRect } from 'konva/lib/types';
* drawing the mask and compositing everything correctly to output a valid * drawing the mask and compositing everything correctly to output a valid
* mask image. * mask image.
*/ */
const generateMask = ( export const getStageDataURL = (stage: Stage, boundingBox: IRect): string => {
// create an offscreen canvas and add the mask to it
// const { stage, offscreenContainer } = buildMaskStage(lines, boundingBox);
const dataURL = stage.toDataURL({ ...boundingBox });
// const imageData = stage
// .toCanvas()
// .getContext('2d')
// ?.getImageData(
// boundingBox.x,
// boundingBox.y,
// boundingBox.width,
// boundingBox.height
// );
// offscreenContainer.remove();
// return { dataURL, imageData };
return dataURL;
};
export const getStageImageData = (
stage: Stage,
boundingBox: IRect
): ImageData | undefined => {
const imageData = stage
.toCanvas()
.getContext('2d')
?.getImageData(
boundingBox.x,
boundingBox.y,
boundingBox.width,
boundingBox.height
);
return imageData;
};
export const buildMaskStage = (
lines: CanvasMaskLine[], lines: CanvasMaskLine[],
boundingBox: IRect boundingBox: IRect
): { dataURL: string; imageData: ImageData } => { ): { stage: Stage; offscreenContainer: HTMLDivElement } => {
// create an offscreen canvas and add the mask to it // create an offscreen canvas and add the mask to it
const { width, height } = boundingBox; const { width, height } = boundingBox;
@ -57,20 +98,5 @@ const generateMask = (
stage.add(baseLayer); stage.add(baseLayer);
stage.add(maskLayer); stage.add(maskLayer);
const dataURL = stage.toDataURL({ ...boundingBox }); return { stage, offscreenContainer };
const imageData = stage
.toCanvas()
.getContext('2d')
?.getImageData(
boundingBox.x,
boundingBox.y,
boundingBox.width,
boundingBox.height
);
offscreenContainer.remove();
return { dataURL, imageData };
}; };
export default generateMask;

View File

@ -1,24 +1,27 @@
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider';
import { isCanvasMaskLine } from '../store/canvasTypes'; import { isCanvasMaskLine } from '../store/canvasTypes';
import generateMask from './generateMask'; import {
buildMaskStage,
getStageDataURL,
getStageImageData,
} from './generateMask';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { import {
areAnyPixelsBlack, areAnyPixelsBlack,
getImageDataTransparency, getImageDataTransparency,
getIsImageDataWhite,
} from 'common/util/arrayBuffer'; } from 'common/util/arrayBuffer';
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { masks } from 'dateformat';
const moduleLog = log.child({ namespace: 'getCanvasDataURLs' });
export const getCanvasDataURLs = (state: RootState) => { export const getCanvasDataURLs = (state: RootState) => {
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
const canvasStage = getCanvasStage(); const canvasStage = getCanvasStage();
if (!canvasBaseLayer || !canvasStage) { if (!canvasBaseLayer || !canvasStage) {
log.error( moduleLog.error('Unable to find canvas / stage');
{ namespace: 'getCanvasDataURLs' },
'Unable to find canvas / stage'
);
return; return;
} }
@ -55,30 +58,46 @@ export const getCanvasDataURLs = (state: RootState) => {
const absPos = canvasBaseLayer.getAbsolutePosition(); const absPos = canvasBaseLayer.getAbsolutePosition();
const { dataURL: maskDataURL, imageData: maskImageData } = generateMask( const offsetBoundingBox = {
isMaskEnabled ? objects.filter(isCanvasMaskLine) : [],
{
x: boundingBox.x + absPos.x, x: boundingBox.x + absPos.x,
y: boundingBox.y + absPos.y, y: boundingBox.y + absPos.y,
width: boundingBox.width, width: boundingBox.width,
height: boundingBox.height, height: boundingBox.height,
} };
const { stage: maskStage, offscreenContainer } = buildMaskStage(
isMaskEnabled ? objects.filter(isCanvasMaskLine) : [],
offsetBoundingBox
); );
const baseDataURL = canvasBaseLayer.toDataURL({ const maskDataURL = maskStage.toDataURL(offsetBoundingBox);
x: boundingBox.x + absPos.x,
y: boundingBox.y + absPos.y, const maskImageData = maskStage
width: boundingBox.width, .toCanvas()
height: boundingBox.height, .getContext('2d')
}); ?.getImageData(
offsetBoundingBox.x,
offsetBoundingBox.y,
offsetBoundingBox.width,
offsetBoundingBox.height
);
offscreenContainer.remove();
if (!maskImageData) {
moduleLog.error('Unable to get mask stage context');
return;
}
const baseDataURL = canvasBaseLayer.toDataURL(offsetBoundingBox);
const ctx = canvasBaseLayer.getContext(); const ctx = canvasBaseLayer.getContext();
const baseImageData = ctx.getImageData( const baseImageData = ctx.getImageData(
boundingBox.x + absPos.x, offsetBoundingBox.x,
boundingBox.y + absPos.y, offsetBoundingBox.y,
boundingBox.width, offsetBoundingBox.width,
boundingBox.height offsetBoundingBox.height
); );
const { const {
@ -86,6 +105,7 @@ export const getCanvasDataURLs = (state: RootState) => {
isFullyTransparent: baseIsFullyTransparent, isFullyTransparent: baseIsFullyTransparent,
} = getImageDataTransparency(baseImageData); } = getImageDataTransparency(baseImageData);
// const doesMaskHaveBlackPixels = false;
const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData);
if (state.system.enableImageDebugging) { if (state.system.enableImageDebugging) {

View File

@ -0,0 +1,102 @@
import { RootState } from 'app/store/store';
import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider';
import {
CanvasObject,
Dimensions,
isCanvasMaskLine,
} from '../store/canvasTypes';
import { buildMaskStage, getStageImageData } from './generateMask';
import { log } from 'app/logging/useLogger';
import {
areAnyPixelsBlack,
getImageDataTransparency,
} from 'common/util/arrayBuffer';
import { getNodeType } from 'features/nodes/util/getNodeType';
import { Vector2d } from 'konva/lib/types';
const moduleLog = log.child({ namespace: 'getCanvasNodeTypes' });
export type GetCanvasNodeTypeArg = {
objects: CanvasObject[];
boundingBoxCoordinates: Vector2d;
boundingBoxDimensions: Dimensions;
stageScale: number;
isMaskEnabled: boolean;
};
export const getCanvasNodeType = (arg: GetCanvasNodeTypeArg) => {
const canvasBaseLayer = getCanvasBaseLayer();
const canvasStage = getCanvasStage();
if (!canvasBaseLayer || !canvasStage) {
moduleLog.error('Unable to find canvas / stage');
return;
}
const {
objects,
boundingBoxCoordinates,
boundingBoxDimensions,
stageScale,
isMaskEnabled,
} = arg;
const boundingBox = {
...boundingBoxCoordinates,
...boundingBoxDimensions,
};
const tempScale = canvasBaseLayer.scale();
canvasBaseLayer.scale({
x: 1 / stageScale,
y: 1 / stageScale,
});
const absPos = canvasBaseLayer.getAbsolutePosition();
const scaledBoundingBox = {
x: boundingBox.x + absPos.x,
y: boundingBox.y + absPos.y,
width: boundingBox.width,
height: boundingBox.height,
};
const { stage: maskStage, offscreenContainer } = buildMaskStage(
isMaskEnabled ? objects.filter(isCanvasMaskLine) : [],
scaledBoundingBox
);
const maskImageData = getStageImageData(maskStage, scaledBoundingBox);
offscreenContainer.remove();
if (!maskImageData) {
moduleLog.error('Unable to get mask stage context');
return;
}
const ctx = canvasBaseLayer.getContext();
const baseImageData = ctx.getImageData(
boundingBox.x + absPos.x,
boundingBox.y + absPos.y,
boundingBox.width,
boundingBox.height
);
canvasBaseLayer.scale(tempScale);
const {
isPartiallyTransparent: baseIsPartiallyTransparent,
isFullyTransparent: baseIsFullyTransparent,
} = getImageDataTransparency(baseImageData);
const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData);
return getNodeType(
baseIsPartiallyTransparent,
baseIsFullyTransparent,
doesMaskHaveBlackPixels
);
};

View File

@ -134,6 +134,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleDragStart = useCallback( const handleDragStart = useCallback(
(e: DragEvent<HTMLDivElement>) => { (e: DragEvent<HTMLDivElement>) => {
console.log('dragging');
e.dataTransfer.setData('invokeai/imageName', image.name); e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type); e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';

View File

@ -34,6 +34,7 @@ import ParametersSlide from '../../common/ParametersSlide';
import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasParameters from './UnifiedCanvasParameters';
import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
import UnifiedCanvasContent from './UnifiedCanvasContent'; import UnifiedCanvasContent from './UnifiedCanvasContent';
import { useGetCanvasNodeType } from 'features/canvas/hooks/useGetCanvasNodeType';
const CanvasWorkspace = () => { const CanvasWorkspace = () => {
const shouldPinParametersPanel = useAppSelector( const shouldPinParametersPanel = useAppSelector(