feat(canvas): build PSD export in the browser from canvas layers
Move PSD generation off the server. The image-generation API only produces raster formats, so drop the PSD request path, the oov/psd decoder, and the LayeredDocumentGenerator wiring. Layer separation now returns clean background, foreground, and text_render_data artifacts into a transparent frame. Export that frame (or any image node) as PSD from the canvas context menu: ag-psd assembles layers, rotation/flip, opacity, editable text, and stroke effects from the current canvas state at download time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -117,7 +117,7 @@ func normalizeInputImageTransport(value string) string {
|
||||
|
||||
func normalizeImageOutputFormat(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "psd", "png", "jpeg", "jpg", "webp":
|
||||
case "png", "jpeg", "jpg", "webp":
|
||||
if strings.EqualFold(strings.TrimSpace(value), "jpg") {
|
||||
return "jpeg"
|
||||
}
|
||||
@@ -136,47 +136,6 @@ func normalizeImageQuality(value string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ImageClient) GenerateLayeredDocument(ctx context.Context, req design.LayeredDocumentRequest) (design.LayeredDocument, error) {
|
||||
imageURL := strings.TrimSpace(req.ImageURL)
|
||||
if imageURL == "" {
|
||||
return design.LayeredDocument{}, fmt.Errorf("layered document image_url is required")
|
||||
}
|
||||
outputFormat := normalizeImageOutputFormat(req.OutputFormat)
|
||||
if outputFormat == "" {
|
||||
outputFormat = "psd"
|
||||
}
|
||||
prompt := strings.TrimSpace(req.Prompt)
|
||||
if prompt == "" {
|
||||
prompt = "Create a layered PSD from the provided image. Preserve the exact canvas and visual design. Put a single clean background layer at the bottom with all text and small foreground elements removed. Put each small standalone foreground element on its own image layer. Keep text as editable text layers when possible. Do not merge text into the background. Return only the layered PSD file."
|
||||
}
|
||||
size := strings.TrimSpace(req.Size)
|
||||
if size == "" {
|
||||
size = "auto"
|
||||
}
|
||||
generated, err := c.Generate(ctx, prompt, ImageRequestOptions{
|
||||
Model: req.Model,
|
||||
Size: size,
|
||||
Count: 1,
|
||||
Images: []string{imageURL},
|
||||
OutputFormat: outputFormat,
|
||||
IdempotencyKey: strings.TrimSpace(req.IdempotencyKey),
|
||||
})
|
||||
if err != nil {
|
||||
return design.LayeredDocument{}, err
|
||||
}
|
||||
content := strings.TrimSpace(generated.URL)
|
||||
if content == "" && len(generated.URLs) > 0 {
|
||||
content = strings.TrimSpace(generated.URLs[0])
|
||||
}
|
||||
if content == "" {
|
||||
return design.LayeredDocument{}, fmt.Errorf("layered document generation returned empty file")
|
||||
}
|
||||
return design.LayeredDocument{
|
||||
Content: content,
|
||||
ContentType: outputFormatContentType(outputFormat),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ImageClient) Generate(ctx context.Context, prompt string, opts ImageRequestOptions) (GeneratedImage, error) {
|
||||
prompt = strings.TrimSpace(prompt)
|
||||
if prompt == "" {
|
||||
@@ -948,8 +907,6 @@ func stripDataURLPrefix(value string) string {
|
||||
|
||||
func outputFormatContentType(format string) string {
|
||||
switch normalizeImageOutputFormat(format) {
|
||||
case "psd":
|
||||
return "image/vnd.adobe.photoshop"
|
||||
case "jpeg":
|
||||
return "image/jpeg"
|
||||
case "webp":
|
||||
|
||||
Reference in New Issue
Block a user