feat: add PSD force clipping mask option
This commit is contained in:
parent
4d2093ef44
commit
db1fc8c3df
@ -1798,6 +1798,9 @@ export function PsdTemplatesSettingsClient({
|
||||
allowUserUploadOverride: checked
|
||||
? false
|
||||
: current.allowUserUploadOverride,
|
||||
forceClipMask: checked
|
||||
? false
|
||||
: current.forceClipMask,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1834,6 +1837,9 @@ export function PsdTemplatesSettingsClient({
|
||||
allowUserUploadOverride: checked
|
||||
? current.allowUserUploadOverride
|
||||
: false,
|
||||
forceClipMask: checked
|
||||
? current.forceClipMask
|
||||
: false,
|
||||
}
|
||||
: {
|
||||
...current,
|
||||
@ -1949,6 +1955,31 @@ export function PsdTemplatesSettingsClient({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between rounded-xl border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-sm font-medium">强制剪切蒙版</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
开启后替换图会按当前 PSD 图层原始透明区域剪切,适合需要贴合固定形状的图片槽位。
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={layer.isEditable && layer.forceClipMask === true}
|
||||
disabled={!layer.isEditable || layer.isSuperLocked === true}
|
||||
onCheckedChange={(checked) =>
|
||||
updateDraftLayer(layer.id, (current) =>
|
||||
current.slotType === "image"
|
||||
? {
|
||||
...current,
|
||||
forceClipMask: current.isEditable
|
||||
? checked
|
||||
: false,
|
||||
}
|
||||
: current
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-xl border p-3">
|
||||
<div className="text-sm font-medium">填充模式</div>
|
||||
<Select
|
||||
|
||||
@ -1476,16 +1476,27 @@ function renderTextLayerPreviewContent(
|
||||
function getAssignedImageMaskStyle(
|
||||
layer: AgentPsdTemplateImageLayerSlot
|
||||
): CSSProperties {
|
||||
const hasMask = Boolean(layer.maskUrl);
|
||||
const fallbackMaskUrl = layer.forceClipMask
|
||||
? getPsdLayerPreviewSourceUrl(layer)
|
||||
: "";
|
||||
const normalizedMaskUrl = String(layer.maskUrl || fallbackMaskUrl || "").trim();
|
||||
const hasMask = Boolean(normalizedMaskUrl);
|
||||
const maskImageUrl = hasMask
|
||||
? `url(${buildPsdLayerPreviewUrl(normalizedMaskUrl, {
|
||||
width: layer.width,
|
||||
quality: 72,
|
||||
})})`
|
||||
: undefined;
|
||||
return {
|
||||
WebkitMaskImage: hasMask ? `url(${layer.maskUrl})` : undefined,
|
||||
maskImage: hasMask ? `url(${layer.maskUrl})` : undefined,
|
||||
WebkitMaskImage: maskImageUrl,
|
||||
maskImage: maskImageUrl,
|
||||
WebkitMaskSize: hasMask ? "100% 100%" : undefined,
|
||||
maskSize: hasMask ? "100% 100%" : undefined,
|
||||
WebkitMaskRepeat: hasMask ? "no-repeat" : undefined,
|
||||
maskRepeat: hasMask ? "no-repeat" : undefined,
|
||||
WebkitMaskPosition: hasMask ? "center" : undefined,
|
||||
maskPosition: hasMask ? "center" : undefined,
|
||||
maskMode: hasMask ? "alpha" : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -4152,6 +4163,9 @@ export function AgentPsdTemplateEditorDialog({
|
||||
...getAssignedImageMaskStyle(layer),
|
||||
}
|
||||
: layerBoxStyle;
|
||||
const previewZIndex = assignedUrl
|
||||
? Math.max(baseZIndex, renderBaseZIndex + 1)
|
||||
: baseZIndex;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -4176,7 +4190,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
}
|
||||
style={{
|
||||
...assignedImageContainerStyle,
|
||||
zIndex: baseZIndex,
|
||||
zIndex: previewZIndex,
|
||||
cursor:
|
||||
isLayerSuperLocked
|
||||
? "default"
|
||||
|
||||
@ -49,6 +49,7 @@ export interface AgentPsdTemplateImageLayerSlot
|
||||
slotType: "image";
|
||||
isDefaultResultSource: boolean;
|
||||
allowUserUploadOverride: boolean;
|
||||
forceClipMask?: boolean;
|
||||
replaceMode: AgentPsdTemplateReplaceMode;
|
||||
contentBounds?: AgentPsdTemplateImageContentBounds;
|
||||
maskUrl?: string;
|
||||
@ -415,6 +416,7 @@ export function parseAgentPsdTemplateLayers(value: unknown) {
|
||||
isEditable && !isSuperLocked && record.isDefaultResultSource === true,
|
||||
allowUserUploadOverride:
|
||||
isEditable && !isSuperLocked ? record.allowUserUploadOverride !== false : false,
|
||||
forceClipMask: isEditable && !isSuperLocked && record.forceClipMask === true,
|
||||
replaceMode: normalizeAgentPsdTemplateReplaceMode(record.replaceMode),
|
||||
contentBounds: normalizeAgentPsdTemplateImageContentBounds(
|
||||
record.contentBounds,
|
||||
|
||||
@ -576,13 +576,13 @@ export async function renderAgentPsdTemplate(params: {
|
||||
for (const layer of orderedTemplateLayers) {
|
||||
if (hiddenLayerIdSet.has(layer.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storedLayerSourceUrl = getStoredLayerSourceUrl(layer);
|
||||
if (shouldRenderAsStaticLayer(layer)) {
|
||||
if (shouldUseRenderBaseFallback || shouldUseRenderSegments) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const storedLayerSourceUrl = getStoredLayerSourceUrl(layer);
|
||||
if (shouldRenderAsStaticLayer(layer)) {
|
||||
if (shouldUseRenderBaseFallback || shouldUseRenderSegments) {
|
||||
continue;
|
||||
}
|
||||
pushAsset(
|
||||
storedLayerSourceUrl,
|
||||
"PSD 图层素材",
|
||||
@ -1032,11 +1032,16 @@ export async function renderAgentPsdTemplate(params: {
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
|
||||
if (layer.maskUrl) {
|
||||
const shouldUseLayerSourceAsMask = !layer.maskUrl && layer.forceClipMask;
|
||||
if (layer.maskUrl || shouldUseLayerSourceAsMask) {
|
||||
try {
|
||||
const maskBuffer = await sharp(
|
||||
await readCachedBuffer(layer.maskUrl, "PSD 蒙版资源")
|
||||
)
|
||||
const rawMaskBuffer = layer.maskUrl
|
||||
? await readCachedBuffer(layer.maskUrl, "PSD 蒙版资源")
|
||||
: await readReusableLayerBuffer(layer);
|
||||
if (!rawMaskBuffer) {
|
||||
throw new Error("Missing PSD layer mask source");
|
||||
}
|
||||
const maskBuffer = await sharp(rawMaskBuffer)
|
||||
.resize({
|
||||
width: Math.max(1, Math.round(layer.width)),
|
||||
height: Math.max(1, Math.round(layer.height)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user