Skip to content

Commit a67efdf

Browse files
perf(ui): optimize curves graph component
Do not use whole layer as trigger for histo recalc; use the canvas cache of the layer - it more reliably indicates when the layer pixel data has changed, and fixes an issue where we can miss the first histo calc due to race conditiong with async layer bbox calculation.
1 parent d6ff9c2 commit a67efdf

File tree

2 files changed

+25
-16
lines changed

2 files changed

+25
-16
lines changed

invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerCurvesAdjustmentsEditor.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Box, Flex } from '@invoke-ai/ui-library';
2+
import { useStore } from '@nanostores/react';
23
import { createSelector } from '@reduxjs/toolkit';
34
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
45
import { useEntityAdapterContext } from 'features/controlLayers/contexts/EntityAdapterContext';
56
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
67
import { rasterLayerAdjustmentsCurvesUpdated } from 'features/controlLayers/store/canvasSlice';
78
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
8-
import type { ChannelName, ChannelPoints } from 'features/controlLayers/store/types';
9+
import type { ChannelName, ChannelPoints, CurvesAdjustmentsConfig } from 'features/controlLayers/store/types';
910
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
1011
import { useTranslation } from 'react-i18next';
1112

@@ -16,6 +17,13 @@ const DEFAULT_POINTS: ChannelPoints = [
1617
[255, 255],
1718
];
1819

20+
const DEFAULT_CURVES: CurvesAdjustmentsConfig = {
21+
master: DEFAULT_POINTS,
22+
r: DEFAULT_POINTS,
23+
g: DEFAULT_POINTS,
24+
b: DEFAULT_POINTS,
25+
};
26+
1927
type ChannelHistograms = Record<ChannelName, number[] | null>;
2028

2129
const calculateHistogramsFromImageData = (imageData: ImageData): ChannelHistograms | null => {
@@ -62,29 +70,29 @@ export const RasterLayerCurvesAdjustmentsEditor = memo(() => {
6270
const entityIdentifier = useEntityIdentifierContext<'raster_layer'>();
6371
const adapter = useEntityAdapterContext<'raster_layer'>('raster_layer');
6472
const { t } = useTranslation();
65-
const selectLayer = useMemo(
66-
() => createSelector(selectCanvasSlice, (canvas) => selectEntity(canvas, entityIdentifier)),
67-
[entityIdentifier]
68-
);
69-
const layer = useAppSelector(selectLayer);
73+
const selectCurves = useMemo(() => {
74+
return createSelector(
75+
selectCanvasSlice,
76+
(canvas) => selectEntity(canvas, entityIdentifier)?.adjustments?.curves ?? DEFAULT_CURVES
77+
);
78+
}, [entityIdentifier]);
79+
const curves = useAppSelector(selectCurves);
80+
7081
const selectIsDisabled = useMemo(() => {
7182
return createSelector(
7283
selectCanvasSlice,
7384
(canvas) => selectEntity(canvas, entityIdentifier)?.adjustments?.enabled !== true
7485
);
7586
}, [entityIdentifier]);
7687
const isDisabled = useAppSelector(selectIsDisabled);
88+
// The canvas cache for the layer serves as a proxy for when the layer changes and can be used to trigger histo recalc
89+
const canvasCache = useStore(adapter.$canvasCache);
7790

7891
const [histMaster, setHistMaster] = useState<number[] | null>(null);
7992
const [histR, setHistR] = useState<number[] | null>(null);
8093
const [histG, setHistG] = useState<number[] | null>(null);
8194
const [histB, setHistB] = useState<number[] | null>(null);
8295

83-
const pointsMaster = layer?.adjustments?.curves.master ?? DEFAULT_POINTS;
84-
const pointsR = layer?.adjustments?.curves.r ?? DEFAULT_POINTS;
85-
const pointsG = layer?.adjustments?.curves.g ?? DEFAULT_POINTS;
86-
const pointsB = layer?.adjustments?.curves.b ?? DEFAULT_POINTS;
87-
8896
const recalcHistogram = useCallback(() => {
8997
try {
9098
const rect = adapter.transformer.getRelativeRect();
@@ -110,7 +118,7 @@ export const RasterLayerCurvesAdjustmentsEditor = memo(() => {
110118

111119
useEffect(() => {
112120
recalcHistogram();
113-
}, [layer?.objects, layer?.adjustments, recalcHistogram]);
121+
}, [canvasCache, recalcHistogram]);
114122

115123
const onChangePoints = useCallback(
116124
(channel: ChannelName, pts: ChannelPoints) => {
@@ -138,28 +146,28 @@ export const RasterLayerCurvesAdjustmentsEditor = memo(() => {
138146
<RasterLayerCurvesAdjustmentsGraph
139147
title={t('controlLayers.adjustments.master')}
140148
channel="master"
141-
points={pointsMaster}
149+
points={curves.master}
142150
histogram={histMaster}
143151
onChange={onChangeMaster}
144152
/>
145153
<RasterLayerCurvesAdjustmentsGraph
146154
title={t('common.red')}
147155
channel="r"
148-
points={pointsR}
156+
points={curves.r}
149157
histogram={histR}
150158
onChange={onChangeR}
151159
/>
152160
<RasterLayerCurvesAdjustmentsGraph
153161
title={t('common.green')}
154162
channel="g"
155-
points={pointsG}
163+
points={curves.g}
156164
histogram={histG}
157165
onChange={onChangeG}
158166
/>
159167
<RasterLayerCurvesAdjustmentsGraph
160168
title={t('common.blue')}
161169
channel="b"
162-
points={pointsB}
170+
points={curves.b}
163171
histogram={histB}
164172
onChange={onChangeB}
165173
/>

invokeai/frontend/web/src/features/controlLayers/store/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ const zChannelName = z.enum(['master', 'r', 'g', 'b']);
404404
const zCurvesAdjustmentsConfig = z.record(zChannelName, zChannelPoints);
405405
export type ChannelName = z.infer<typeof zChannelName>;
406406
export type ChannelPoints = z.infer<typeof zChannelPoints>;
407+
export type CurvesAdjustmentsConfig = z.infer<typeof zCurvesAdjustmentsConfig>;
407408

408409
/**
409410
* The curves adjustments are stored as LUTs in the Konva node attributes. Konva will use these values when applying

0 commit comments

Comments
 (0)