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:
@@ -520,7 +520,8 @@ function createMaskVisualizationProgram(gl: WebGL2RenderingContext): MaskVisuali
|
||||
in vec2 v_texCoord;
|
||||
out vec4 outColor;
|
||||
void main() {
|
||||
float maskAlpha = texture(u_mask, v_texCoord).a;
|
||||
vec4 maskColor = texture(u_mask, v_texCoord);
|
||||
float maskAlpha = maskColor.a * dot(maskColor.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
if (u_mode == 0) {
|
||||
float value = step(0.5, maskAlpha);
|
||||
outColor = vec4(value, value, value, 1.0);
|
||||
@@ -642,7 +643,9 @@ function createMaskRevealPreviewProgram(gl: WebGL2RenderingContext) {
|
||||
out vec4 outColor;
|
||||
void main() {
|
||||
vec4 color = texture(u_image, v_texCoord);
|
||||
float hiddenMaskAlpha = 1.0 - texture(u_mask, v_maskTexCoord).a;
|
||||
vec4 maskColor = texture(u_mask, v_maskTexCoord);
|
||||
float maskAlpha = maskColor.a * dot(maskColor.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
float hiddenMaskAlpha = 1.0 - maskAlpha;
|
||||
float previewAlpha = clamp(hiddenMaskAlpha * u_opacity, 0.0, 1.0);
|
||||
outColor = vec4(color.rgb * previewAlpha, color.a * previewAlpha);
|
||||
}`,
|
||||
@@ -693,7 +696,8 @@ function createMaskedProgram(gl: WebGL2RenderingContext) {
|
||||
out vec4 outColor;
|
||||
void main() {
|
||||
vec4 color = texture(u_image, v_texCoord);
|
||||
float maskAlpha = texture(u_mask, v_maskTexCoord).a;
|
||||
vec4 maskColor = texture(u_mask, v_maskTexCoord);
|
||||
float maskAlpha = maskColor.a * dot(maskColor.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
float alpha = color.a * maskAlpha;
|
||||
outColor = vec4(color.rgb * maskAlpha, alpha);
|
||||
}`,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { renderArtboard } from "./artboard";
|
||||
import { createBrushPreviewRenderer } from "./brush-preview";
|
||||
import { createCheckerboardRenderer } from "./checkerboard";
|
||||
import { createImageTextureRenderer } from "./image-textures";
|
||||
import { renderLayers } from "./layers";
|
||||
import { generationCandidatePreviewAssets, renderLayers } from "./layers";
|
||||
import { renderSelectionOverlay } from "./selection";
|
||||
import { renderTransformControls } from "./transform-controls";
|
||||
import type { WebGlRendererContext } from "./types";
|
||||
@@ -62,7 +62,7 @@ export function createRenderer(canvas: HTMLCanvasElement, backend: RendererBacke
|
||||
for (const artboard of frame.document.artboards) {
|
||||
if (artboard.visible) renderArtboard(rendererContext, artboard, frame.editor.viewport, checkerboardRenderer);
|
||||
}
|
||||
imageTextureRenderer.syncAssets(frame.document.assets);
|
||||
imageTextureRenderer.syncAssets([...frame.document.assets, ...generationCandidatePreviewAssets(frame.editor)]);
|
||||
renderLayers(rendererContext, frame.document, frame.editor, imageTextureRenderer);
|
||||
|
||||
if (!frame.editor.maskEdit) {
|
||||
|
||||
Reference in New Issue
Block a user