Files
aego/art/thumbnail.go
sanspie a46347e2c6
ci-platforms / darwin arm64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m19s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 23m57s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
editor: add art studio and stage 12.5 workflows
2026-11-08 18:47:57 +03:00

80 lines
2.3 KiB
Go

package art
import (
"fmt"
"image"
)
// Thumbnail samples a layer (or isolated group) using the same CPU color and
// blend rules as the canvas. It never allocates or decodes a full-size frame.
// Visibility of the selected layer is ignored so hidden layers remain visible
// in the layer list; visibility of children and reference policy are respected.
func (d *Document) Thumbnail(layer, frame ID, o RenderOptions, size int) (*image.NRGBA, error) {
if size < 1 || size > 256 {
return nil, ErrLimit
}
d.mu.Lock()
if d.closed {
d.mu.Unlock()
return nil, ErrClosed
}
s := d.current
d.mu.Unlock()
o = options(o)
if _, ok := s.frame(frame); !ok || !validChannel(o.Channel) {
return nil, fmt.Errorf("art: invalid thumbnail frame or channel")
}
li, ok := s.layer(layer)
if !ok {
return nil, fmt.Errorf("art: missing thumbnail layer")
}
cels := make(map[ID]Cel)
for _, c := range s.Cels {
if c.Frame == frame && c.Channel == o.Channel {
cels[c.Layer] = c
}
}
children := make(map[ID][]int)
for i, l := range s.Layers {
children[l.Parent] = append(children[l.Parent], i)
}
var sample func(int, int, int, int) Color
sample = func(index, x, y, depth int) Color {
if depth > len(s.Layers) {
return Color{}
}
l := s.Layers[index]
if (index != li && !l.Visible) || (l.Kind == Reference && !o.References) {
return Color{}
}
v := Color{}
if l.Kind == Group {
for _, child := range children[l.ID] {
v = blendPixel(v, sample(child, x, y, depth+1), s.Layers[child].Opacity, s.Layers[child].Blend)
}
} else if c, exists := cels[l.ID]; exists {
v = decode(s.forChannel(c.Channel), s.Images[c.Image].at(x-c.X, y-c.Y))
v.A = uint8((uint32(v.A)*uint32(c.Opacity) + 127) / 255)
}
return v
}
w, h := s.Canvas.Width, s.Canvas.Height
dw, dh := size, size
if w > h {
dh = max(1, h*size/w)
} else {
dw = max(1, w*size/h)
}
ox, oy := (size-dw)/2, (size-dh)/2
out := image.NewNRGBA(image.Rect(0, 0, size, size))
for y := 0; y < dh; y++ {
for x := 0; x < dw; x++ {
v := sample(li, min(w-1, (2*x+1)*w/(2*dw)), min(h-1, (2*y+1)*h/(2*dh)), 0)
v = blendPixel(Color{}, v, s.Layers[li].Opacity, s.Layers[li].Blend)
i := out.PixOffset(x+ox, y+oy)
out.Pix[i], out.Pix[i+1], out.Pix[i+2], out.Pix[i+3] = v.R, v.G, v.B, v.A
}
}
return out, nil
}