mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): mask layers choose own opacity
This commit is contained in:
parent
43b3fab6be
commit
cb293fd7ac
@ -12,7 +12,6 @@ import {
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { MaskOpacity } from 'features/controlLayers/components/MaskOpacity';
|
||||
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import {
|
||||
clipToBboxChanged,
|
||||
@ -64,7 +63,6 @@ const ControlLayersSettingsPopover = () => {
|
||||
<PopoverContent>
|
||||
<PopoverBody>
|
||||
<Flex direction="column" gap={2}>
|
||||
<MaskOpacity />
|
||||
<FormControl w="full">
|
||||
<FormLabel flexGrow={1}>{t('unifiedCanvas.invertBrushSizeScrollDirection')}</FormLabel>
|
||||
<Checkbox isChecked={invertScroll} onChange={onChangeInvertScroll} />
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
|
||||
import { imFillColorChanged, imFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { FillStyle } from 'features/controlLayers/store/types';
|
||||
import type { FillStyle, RgbaColor } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { RgbColor } from 'react-colorful';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const InpaintMaskMaskFillColorPicker = memo(() => {
|
||||
@ -15,7 +14,7 @@ export const InpaintMaskMaskFillColorPicker = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const fill = useAppSelector((s) => s.canvasV2.inpaintMask.fill);
|
||||
const onChangeFillColor = useCallback(
|
||||
(color: RgbColor) => {
|
||||
(color: RgbaColor) => {
|
||||
dispatch(imFillColorChanged({ color }));
|
||||
},
|
||||
[dispatch]
|
||||
@ -44,7 +43,7 @@ export const InpaintMaskMaskFillColorPicker = memo(() => {
|
||||
<PopoverContent>
|
||||
<PopoverBody minH={64}>
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<RgbColorPicker color={fill.color} onChange={onChangeFillColor} withNumberInput />
|
||||
<IAIColorPicker color={fill.color} onChange={onChangeFillColor} withNumberInput />
|
||||
<MaskFillStyle style={fill.style} onChange={onChangeFillStyle} />
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { maskOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
export const MaskOpacity = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const opacity = useAppSelector((s) => Math.round(s.canvasV2.settings.maskOpacity * 100));
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(maskOpacityChanged(Math.max(v / 100, 0.25)));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
return (
|
||||
<FormControl orientation="vertical">
|
||||
<FormLabel m={0}>{t('controlLayers.globalMaskOpacity')}</FormLabel>
|
||||
<Flex gap={4}>
|
||||
<CompositeSlider
|
||||
min={25}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={0.3}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
minW={48}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
min={25}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={0.3}
|
||||
onChange={onChange}
|
||||
w={28}
|
||||
format={formatPct}
|
||||
/>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
MaskOpacity.displayName = 'MaskOpacity';
|
@ -1,15 +1,14 @@
|
||||
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import type { FillStyle } from 'features/controlLayers/store/types';
|
||||
import type { FillStyle, RgbaColor } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { RgbColor } from 'react-colorful';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const RegionalGuidanceMaskFillColorPicker = memo(() => {
|
||||
@ -18,7 +17,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const fill = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, entityIdentifier.id).fill);
|
||||
const onChangeFillColor = useCallback(
|
||||
(color: RgbColor) => {
|
||||
(color: RgbaColor) => {
|
||||
dispatch(rgFillColorChanged({ id: entityIdentifier.id, color }));
|
||||
},
|
||||
[dispatch, entityIdentifier.id]
|
||||
@ -47,7 +46,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => {
|
||||
<PopoverContent>
|
||||
<PopoverBody minH={64}>
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<RgbColorPicker color={fill.color} onChange={onChangeFillColor} withNumberInput />
|
||||
<IAIColorPicker color={fill.color} onChange={onChangeFillColor} withNumberInput />
|
||||
<MaskFillStyle style={fill.style} onChange={onChangeFillStyle} />
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
|
@ -323,7 +323,6 @@ export class CanvasManager {
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.regions.entities !== this._prevState.regions.entities ||
|
||||
state.settings.maskOpacity !== this._prevState.settings.maskOpacity ||
|
||||
state.tool.selected !== this._prevState.tool.selected ||
|
||||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
||||
) {
|
||||
@ -355,7 +354,6 @@ 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
|
||||
) {
|
||||
|
@ -22,7 +22,6 @@ export class CanvasMaskAdapter {
|
||||
log: Logger;
|
||||
|
||||
state: CanvasInpaintMaskState | CanvasRegionalGuidanceState;
|
||||
maskOpacity: number;
|
||||
|
||||
transformer: CanvasTransformer;
|
||||
renderer: CanvasObjectRenderer;
|
||||
@ -54,8 +53,6 @@ export class CanvasMaskAdapter {
|
||||
|
||||
this.renderer = new CanvasObjectRenderer(this);
|
||||
this.transformer = new CanvasTransformer(this);
|
||||
|
||||
this.maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,14 +76,8 @@ export class CanvasMaskAdapter {
|
||||
isSelected: boolean;
|
||||
}) => {
|
||||
const state = get(arg, 'state', this.state);
|
||||
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
|
||||
if (
|
||||
!this.isFirstRender &&
|
||||
state === this.state &&
|
||||
state.fill === this.state.fill &&
|
||||
maskOpacity === this.maskOpacity
|
||||
) {
|
||||
if (!this.isFirstRender && state === this.state && state.fill === this.state.fill) {
|
||||
this.log.trace('State unchanged, skipping update');
|
||||
return;
|
||||
}
|
||||
@ -107,10 +98,6 @@ export class CanvasMaskAdapter {
|
||||
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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
@ -149,7 +149,7 @@ export class CanvasObjectRenderer {
|
||||
this.subscriptions.add(
|
||||
this.manager.stateApi.$stageAttrs.listen(() => {
|
||||
if (this.konva.compositing && this.parent.type === 'mask_adapter') {
|
||||
this.updateCompositingRect(this.parent.state.fill, this.manager.stateApi.getMaskOpacity());
|
||||
this.updateCompositingRect(this.parent.state.fill);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -183,14 +183,13 @@ export class CanvasObjectRenderer {
|
||||
return didRender;
|
||||
};
|
||||
|
||||
updateCompositingRect = (fill: Fill, opacity: number) => {
|
||||
updateCompositingRect = (fill: Fill) => {
|
||||
this.log.trace('Updating compositing rect');
|
||||
assert(this.konva.compositing, 'Missing compositing rect');
|
||||
|
||||
const { x, y, width, height, scale } = this.manager.stateApi.$stageAttrs.get();
|
||||
console.log('stageAttrs', this.manager.stateApi.$stageAttrs.get());
|
||||
|
||||
const attrs: RectConfig = {
|
||||
opacity,
|
||||
x: -x / scale,
|
||||
y: -y / scale,
|
||||
width: width / scale,
|
||||
@ -198,7 +197,7 @@ export class CanvasObjectRenderer {
|
||||
};
|
||||
|
||||
if (fill.style === 'solid') {
|
||||
attrs.fill = rgbColorToString(fill.color);
|
||||
attrs.fill = rgbaColorToString(fill.color);
|
||||
attrs.fillPriority = 'color';
|
||||
this.konva.compositing.rect.setAttrs(attrs);
|
||||
} else {
|
||||
|
@ -171,9 +171,6 @@ export class CanvasStateApi {
|
||||
getInpaintMaskState = () => {
|
||||
return this.getState().inpaintMask;
|
||||
};
|
||||
getMaskOpacity = () => {
|
||||
return this.getState().settings.maskOpacity;
|
||||
};
|
||||
getSession = () => {
|
||||
return this.getState().session;
|
||||
};
|
||||
@ -232,26 +229,23 @@ export class CanvasStateApi {
|
||||
let currentFill: RgbaColor = state.tool.fill;
|
||||
const selectedEntity = this.getSelectedEntity();
|
||||
if (selectedEntity) {
|
||||
// These two entity types use a compositing rect for opacity. Their fill is always white.
|
||||
// These two entity types use a compositing rect for opacity. Their fill is always a solid color.
|
||||
if (selectedEntity.state.type === 'regional_guidance' || selectedEntity.state.type === 'inpaint_mask') {
|
||||
currentFill = RGBA_RED;
|
||||
// currentFill = RGBA_WHITE;
|
||||
}
|
||||
}
|
||||
return currentFill;
|
||||
};
|
||||
|
||||
getBrushPreviewFill = () => {
|
||||
const state = this.getState();
|
||||
let currentFill: RgbaColor = state.tool.fill;
|
||||
getBrushPreviewFill = (): RgbaColor => {
|
||||
const selectedEntity = this.getSelectedEntity();
|
||||
if (selectedEntity) {
|
||||
if (selectedEntity?.state.type === 'regional_guidance' || selectedEntity?.state.type === 'inpaint_mask') {
|
||||
// The brush should use the mask opacity for these entity types
|
||||
if (selectedEntity.state.type === 'regional_guidance' || selectedEntity.state.type === 'inpaint_mask') {
|
||||
currentFill = { ...selectedEntity.state.fill.color, a: this.getSettings().maskOpacity };
|
||||
}
|
||||
return selectedEntity.state.fill.color;
|
||||
} else {
|
||||
const state = this.getState();
|
||||
return state.tool.fill;
|
||||
}
|
||||
return currentFill;
|
||||
};
|
||||
|
||||
$transformingEntity = $transformingEntity;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import type { FillStyle, RgbColor } from 'features/controlLayers/store/types';
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import type { FillStyle, RgbaColor } from 'features/controlLayers/store/types';
|
||||
|
||||
import crosshatch from './pattern-crosshatch.svg?raw';
|
||||
import diagonal from './pattern-diagonal.svg?raw';
|
||||
@ -7,7 +7,7 @@ import grid from './pattern-grid.svg?raw';
|
||||
import horizontal from './pattern-horizontal.svg?raw';
|
||||
import vertical from './pattern-vertical.svg?raw';
|
||||
|
||||
export function getPatternSVG(pattern: Exclude<FillStyle, 'solid'>, color: RgbColor) {
|
||||
export function getPatternSVG(pattern: Exclude<FillStyle, 'solid'>, color: RgbaColor) {
|
||||
let content: string = 'data:image/svg+xml;utf8,';
|
||||
if (pattern === 'crosshatch') {
|
||||
content += crosshatch;
|
||||
@ -21,7 +21,7 @@ export function getPatternSVG(pattern: Exclude<FillStyle, 'solid'>, color: RgbCo
|
||||
content += grid;
|
||||
}
|
||||
|
||||
content = content.replaceAll('stroke:black', `stroke:${rgbColorToString(color)}`);
|
||||
content = content.replaceAll('stroke:black', `stroke:${rgbaColorToString(color)}`);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import type {
|
||||
FilterConfig,
|
||||
StageAttrs,
|
||||
} from './types';
|
||||
import { IMAGE_FILTERS, isDrawableEntity, RGBA_RED } from './types';
|
||||
import { IMAGE_FILTERS, isDrawableEntity } from './types';
|
||||
|
||||
const initialState: CanvasV2State = {
|
||||
_version: 3,
|
||||
@ -55,7 +55,7 @@ const initialState: CanvasV2State = {
|
||||
type: 'inpaint_mask',
|
||||
fill: {
|
||||
style: 'diagonal',
|
||||
color: RGBA_RED,
|
||||
color: { r: 255, g: 122, b: 0, a: 1 }, // some orange color
|
||||
},
|
||||
rasterizationCache: [],
|
||||
isEnabled: true,
|
||||
@ -69,7 +69,7 @@ const initialState: CanvasV2State = {
|
||||
selected: 'view',
|
||||
selectedBuffer: null,
|
||||
invertScroll: false,
|
||||
fill: RGBA_RED,
|
||||
fill: { r: 31, g: 160, b: 224, a: 1 }, // invokeBlue.500
|
||||
brush: {
|
||||
width: 50,
|
||||
},
|
||||
@ -87,7 +87,6 @@ const initialState: CanvasV2State = {
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
maskOpacity: 0.3,
|
||||
// TODO(psyche): These are copied from old canvas state, need to be implemented
|
||||
autoSave: false,
|
||||
imageSmoothing: true,
|
||||
@ -471,7 +470,6 @@ export const {
|
||||
invertScrollChanged,
|
||||
toolChanged,
|
||||
toolBufferChanged,
|
||||
maskOpacityChanged,
|
||||
allEntitiesDeleted,
|
||||
clipToBboxChanged,
|
||||
canvasReset,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type { CanvasInpaintMaskState, CanvasV2State, FillStyle } from 'features/controlLayers/store/types';
|
||||
import type { RgbColor } from 'react-colorful';
|
||||
import type { CanvasInpaintMaskState, CanvasV2State, FillStyle, RgbaColor } from 'features/controlLayers/store/types';
|
||||
|
||||
export const inpaintMaskReducers = {
|
||||
imRecalled: (state, action: PayloadAction<{ data: CanvasInpaintMaskState }>) => {
|
||||
@ -8,7 +7,7 @@ export const inpaintMaskReducers = {
|
||||
state.inpaintMask = data;
|
||||
state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id };
|
||||
},
|
||||
imFillColorChanged: (state, action: PayloadAction<{ color: RgbColor }>) => {
|
||||
imFillColorChanged: (state, action: PayloadAction<{ color: RgbaColor }>) => {
|
||||
const { color } = action.payload;
|
||||
state.inpaintMask.fill.color = color;
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ import type {
|
||||
FillStyle,
|
||||
IPMethodV2,
|
||||
RegionalGuidanceIPAdapterConfig,
|
||||
RgbaColor,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
@ -14,7 +15,7 @@ import { isEqual } from 'lodash-es';
|
||||
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type { CanvasRegionalGuidanceState, RgbColor } from './types';
|
||||
import type { CanvasRegionalGuidanceState } from './types';
|
||||
|
||||
export const selectRegionalGuidanceEntity = (state: CanvasV2State, id: string) => {
|
||||
return state.regions.entities.find((rg) => rg.id === id);
|
||||
@ -32,17 +33,17 @@ export const selectRegionalGuidanceEntityOrThrow = (state: CanvasV2State, id: st
|
||||
return rg;
|
||||
};
|
||||
|
||||
const DEFAULT_MASK_COLORS: RgbColor[] = [
|
||||
{ r: 121, g: 157, b: 219 }, // rgb(121, 157, 219)
|
||||
{ r: 131, g: 214, b: 131 }, // rgb(131, 214, 131)
|
||||
{ r: 250, g: 225, b: 80 }, // rgb(250, 225, 80)
|
||||
{ r: 220, g: 144, b: 101 }, // rgb(220, 144, 101)
|
||||
{ r: 224, g: 117, b: 117 }, // rgb(224, 117, 117)
|
||||
{ r: 213, g: 139, b: 202 }, // rgb(213, 139, 202)
|
||||
{ r: 161, g: 120, b: 214 }, // rgb(161, 120, 214)
|
||||
const DEFAULT_MASK_COLORS: RgbaColor[] = [
|
||||
{ r: 121, g: 157, b: 219, a: 0.5 }, // rgb(121, 157, 219)
|
||||
{ r: 131, g: 214, b: 131, a: 0.5 }, // rgb(131, 214, 131)
|
||||
{ r: 250, g: 225, b: 80, a: 0.5 }, // rgb(250, 225, 80)
|
||||
{ r: 220, g: 144, b: 101, a: 0.5 }, // rgb(220, 144, 101)
|
||||
{ r: 224, g: 117, b: 117, a: 0.5 }, // rgb(224, 117, 117)
|
||||
{ r: 213, g: 139, b: 202, a: 0.5 }, // rgb(213, 139, 202)
|
||||
{ r: 161, g: 120, b: 214, a: 0.5 }, // rgb(161, 120, 214)
|
||||
];
|
||||
|
||||
const getRGMaskFill = (state: CanvasV2State): RgbColor => {
|
||||
const getRGMaskFill = (state: CanvasV2State): RgbaColor => {
|
||||
const lastFill = state.regions.entities.slice(-1)[0]?.fill.color;
|
||||
let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill));
|
||||
if (i === -1) {
|
||||
@ -104,7 +105,7 @@ export const regionsReducers = {
|
||||
}
|
||||
entity.negativePrompt = prompt;
|
||||
},
|
||||
rgFillColorChanged: (state, action: PayloadAction<{ id: string; color: RgbColor }>) => {
|
||||
rgFillColorChanged: (state, action: PayloadAction<{ id: string; color: RgbaColor }>) => {
|
||||
const { id, color } = action.payload;
|
||||
const entity = selectRegionalGuidanceEntity(state, id);
|
||||
if (!entity) {
|
||||
|
@ -2,9 +2,6 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type { CanvasV2State } from 'features/controlLayers/store/types';
|
||||
|
||||
export const settingsReducers = {
|
||||
maskOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||
state.settings.maskOpacity = action.payload;
|
||||
},
|
||||
clipToBboxChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.settings.clipToBbox = action.payload;
|
||||
},
|
||||
|
@ -644,7 +644,7 @@ const zMaskObject = z
|
||||
const zFillStyle = z.enum(['solid', 'grid', 'crosshatch', 'diagonal', 'horizontal', 'vertical']);
|
||||
export type FillStyle = z.infer<typeof zFillStyle>;
|
||||
export const isFillStyle = (v: unknown): v is FillStyle => zFillStyle.safeParse(v).success;
|
||||
const zFill = z.object({ style: zFillStyle, color: zRgbColor });
|
||||
const zFill = z.object({ style: zFillStyle, color: zRgbaColor });
|
||||
export type Fill = z.infer<typeof zFill>;
|
||||
|
||||
const zImageCache = z.object({
|
||||
@ -858,7 +858,6 @@ export type CanvasV2State = {
|
||||
};
|
||||
settings: {
|
||||
imageSmoothing: boolean;
|
||||
maskOpacity: number;
|
||||
showHUD: boolean;
|
||||
autoSave: boolean;
|
||||
preserveMaskedArea: boolean;
|
||||
|
Loading…
Reference in New Issue
Block a user