feat: implement inpainting functionality with mask handling
- Added `createMaskedPixelReplacementSource` function to handle pixel replacement using inpainting. - Introduced `buildInpaintBundle` to prepare inpainting data including mask generation and validation. - Created utility functions for mask operations such as `applyMaskedContentModeToRgba`, `expandRectWithinBounds`, and others for mask manipulation. - Developed tests for inpainting preparation and mask raster utilities to ensure functionality and correctness. - Implemented mask raster operations including inversion, feathering, blurring, and more.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import type { Asset } from "@core/asset";
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { Rect } from "@core/geometry";
|
||||
import type { Layer } from "@core/layer";
|
||||
import { createDocumentReadIndex, forEachLayerBackToFront, resolveIndexedLayerBounds, type DocumentReadIndex } from "@editor/document-indexes";
|
||||
import type { EditorState, MaskViewMode, ViewportState } from "@editor/state";
|
||||
import type { EditorState, GenerationCandidate, MaskViewMode, ViewportState } from "@editor/state";
|
||||
import { clearScreenRect } from "./clear-rect";
|
||||
import type { ImageTextureRenderer } from "./image-textures";
|
||||
import { documentRectToScreenRect } from "./screen-rect";
|
||||
@@ -14,15 +16,21 @@ const maskRevealPreviewOpacity = 0.28;
|
||||
|
||||
export function renderLayers(context: WebGlRendererContext, document: ImageDocument, editor: EditorState, imageTextureRenderer: ImageTextureRenderer) {
|
||||
const documentIndex = createDocumentReadIndex(document);
|
||||
const generationCandidate = selectedGenerationCandidate(editor);
|
||||
|
||||
for (const artboard of document.artboards) {
|
||||
if (!artboard.visible) continue;
|
||||
const clipRect = documentRectToScreenRect(context.canvas, artboard.bounds, editor.viewport);
|
||||
const maskLayerIds = documentIndex.maskLayerIdsByArtboardId.get(artboard.id) ?? emptyLayerIds;
|
||||
forEachLayerBackToFront(artboard.layers, (layer) => renderLayer(context, documentIndex, editor, layer, imageTextureRenderer, clipRect, maskLayerIds));
|
||||
if (generationCandidate?.placement.artboardId === artboard.id) renderGenerationCandidatePreview(context, editor, generationCandidate, imageTextureRenderer, clipRect);
|
||||
}
|
||||
}
|
||||
|
||||
export function generationCandidatePreviewAssets(editor: EditorState): Asset[] {
|
||||
return editor.generation.candidates.map((candidate) => generationCandidateAsset(candidate));
|
||||
}
|
||||
|
||||
function renderLayer(
|
||||
context: WebGlRendererContext,
|
||||
documentIndex: DocumentReadIndex,
|
||||
@@ -102,6 +110,44 @@ function isIsolatedMaskView(mode: MaskViewMode) {
|
||||
return mode === "blackWhite" || mode === "alpha" || mode === "overlay";
|
||||
}
|
||||
|
||||
function renderGenerationCandidatePreview(
|
||||
context: WebGlRendererContext,
|
||||
editor: EditorState,
|
||||
candidate: GenerationCandidate,
|
||||
imageTextureRenderer: ImageTextureRenderer,
|
||||
clipRect: ScreenRect,
|
||||
) {
|
||||
const rect = documentRectToScreenRect(context.canvas, generationCandidateBounds(candidate), editor.viewport);
|
||||
imageTextureRenderer.render(generationCandidateAsset(candidate), rect, clipRect);
|
||||
}
|
||||
|
||||
function selectedGenerationCandidate(editor: EditorState): GenerationCandidate | undefined {
|
||||
return editor.generation.candidates.find((candidate) => candidate.id === editor.generation.selectedCandidateId) ?? editor.generation.candidates[0];
|
||||
}
|
||||
|
||||
function generationCandidateAsset(candidate: GenerationCandidate): Asset {
|
||||
return {
|
||||
id: generationCandidateAssetId(candidate.id),
|
||||
name: candidate.placement.layerName,
|
||||
mimeType: candidate.mimeType,
|
||||
source: candidate.source,
|
||||
intrinsicSize: candidate.intrinsicSize,
|
||||
};
|
||||
}
|
||||
|
||||
function generationCandidateAssetId(candidateId: string) {
|
||||
return `generation-candidate:${candidateId}`;
|
||||
}
|
||||
|
||||
function generationCandidateBounds(candidate: GenerationCandidate): Rect {
|
||||
return {
|
||||
x: candidate.placement.transform.position.x,
|
||||
y: candidate.placement.transform.position.y,
|
||||
w: candidate.intrinsicSize.w * candidate.placement.transform.scale.x,
|
||||
h: candidate.intrinsicSize.h * candidate.placement.transform.scale.y,
|
||||
};
|
||||
}
|
||||
|
||||
function assetWithBrushStrokePreview<TAsset extends ImageDocument["assets"][number] | undefined>(asset: TAsset, editor: EditorState): TAsset {
|
||||
if (!asset || editor.brushStrokePreview?.assetId !== asset.id) return asset;
|
||||
return { ...asset, source: editor.brushStrokePreview.source } as TAsset;
|
||||
|
||||
Reference in New Issue
Block a user