166 lines
5.2 KiB
Go
166 lines
5.2 KiB
Go
package editor
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"image"
|
|
|
|
"aego/art"
|
|
"aego/asset"
|
|
"aego/core/mathx"
|
|
"aego/engine/i18n"
|
|
"aego/gpu"
|
|
"aego/render/texture"
|
|
"aego/ui/core"
|
|
"aego/ui/widgets"
|
|
)
|
|
|
|
type artToolSampleKey struct {
|
|
session *art.Session
|
|
brushRevision uint64
|
|
brush art.Brush
|
|
tool int
|
|
filled bool
|
|
channel art.Channel
|
|
mode art.Mode
|
|
palette [32]byte
|
|
}
|
|
|
|
type artToolSample struct {
|
|
key artToolSampleKey
|
|
valid bool
|
|
device uint64
|
|
pixels *imagePixels
|
|
display imageDisplay
|
|
err error
|
|
}
|
|
|
|
func artSampleOperation(tool int) (string, []image.Point, bool) {
|
|
switch tool {
|
|
case 0, 1:
|
|
return "stroke", []image.Point{{8, 43}, {28, 26}, {47, 34}, {70, 16}, {96, 29}, {119, 17}}, true
|
|
case 2:
|
|
return "fill", []image.Point{{64, 32}}, true
|
|
case 3:
|
|
return "line", []image.Point{{9, 48}, {118, 15}}, true
|
|
case 4:
|
|
return "rectangle", []image.Point{{18, 12}, {109, 51}}, true
|
|
case 5:
|
|
return "ellipse", []image.Point{{18, 12}, {109, 51}}, true
|
|
case 6:
|
|
return "gradient", []image.Point{{8, 32}, {119, 32}}, true
|
|
case artCurve:
|
|
return "curve", []image.Point{{8, 44}, {119, 20}, {30, 4}, {91, 59}}, true
|
|
case artPolygon:
|
|
return "polygon", []image.Point{{12, 46}, {36, 13}, {73, 25}, {110, 11}, {117, 49}}, true
|
|
default:
|
|
return "", nil, false
|
|
}
|
|
}
|
|
|
|
// Generate an example with the actual Core operations in a small, private
|
|
// document. The active layer, selection, pixels and Undo history are untouched.
|
|
func artRenderToolSample(s *art.Session, brush art.Brush, tool int, filled bool) (*image.NRGBA, error) {
|
|
kind, points, ok := artSampleOperation(tool)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
mode := s.Document.Info().Canvas.Mode
|
|
if s.Channel != art.Albedo || tool == 1 {
|
|
mode = art.RGBA
|
|
}
|
|
doc, err := art.New(128, 64, mode, art.DefaultLimits())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer doc.Close()
|
|
info := doc.Info()
|
|
edit, err := doc.Begin(doc.Revision(), "Tool sample")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer edit.Cancel()
|
|
if mode == art.Indexed {
|
|
if err := edit.ReplacePalette(s.Document.Info().Palette, false); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
cel := info.Cels[0].ID
|
|
if tool == 1 {
|
|
// Erasing transparent pixels is invisible, so show it on a neutral
|
|
// opaque sample; the checkerboard behind the sample reveals the result.
|
|
if err := edit.Fill(context.Background(), cel, 0, 0, 0, art.Paint{Color: art.Color{R: 112, G: 146, B: 184, A: 255}}, nil, true); err != nil {
|
|
return nil, err
|
|
}
|
|
if brush.Smudge {
|
|
contrast := art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 232, G: 120, B: 78, A: 255}}}
|
|
if err := edit.Shape(cel, "rectangle", image.Pt(0, 0), image.Pt(63, 63), contrast, nil, true); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
brush.Erase = true
|
|
}
|
|
_, err = edit.Execute(context.Background(), art.Operation{Kind: kind, Cel: cel, Points: points, Brush: brush, Filled: filled}, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return edit.Render(info.Frames[0].ID, art.RenderOptions{Channel: art.Albedo})
|
|
}
|
|
|
|
func (p *artPanel) toolStrokePreview(ui *core.Context) {
|
|
v, s := p.view, p.model.Session
|
|
if _, _, ok := artSampleOperation(v.tool); !ok {
|
|
widgets.LabelWrapped(ui, i18n.Tr("editor.art.tool_no_stroke", "This tool does not paint a stroke."), true)
|
|
return
|
|
}
|
|
widgets.LabelDim(ui, i18n.Tr("editor.art.stroke_preview", "Stroke preview"))
|
|
brush := p.currentBrush()
|
|
info := s.Document.Info()
|
|
key := artToolSampleKey{session: s, brushRevision: s.BrushRevision(), brush: brush, tool: v.tool, filled: v.filled, channel: s.Channel, mode: info.Canvas.Mode}
|
|
if info.Canvas.Mode == art.Indexed && s.Channel == art.Albedo {
|
|
colors := make([]byte, 0, len(info.Palette)*4)
|
|
for _, swatch := range info.Palette {
|
|
c := swatch.Color
|
|
colors = append(colors, c.R, c.G, c.B, c.A)
|
|
}
|
|
key.palette = sha256.Sum256(colors)
|
|
}
|
|
sample := &v.toolSample
|
|
device, lost := imageDevice(p.ctx)
|
|
if lost {
|
|
sample.display.close(p.ctx)
|
|
return
|
|
}
|
|
if !sample.valid || sample.key != key || sample.device != device {
|
|
sample.display.close(p.ctx)
|
|
sample.key, sample.device, sample.valid = key, device, true
|
|
sample.pixels, sample.err = nil, nil
|
|
resolved, err := s.PreviewBrush(brush)
|
|
if err == nil {
|
|
var pixels *image.NRGBA
|
|
pixels, err = artRenderToolSample(s, resolved, v.tool, v.filled)
|
|
if err == nil && pixels != nil {
|
|
sample.pixels = &imagePixels{data: texture.Data{Width: pixels.Bounds().Dx(), Height: pixels.Bounds().Dy(), Format: gpu.TexSRGBA8, Pixels: pixels.Pix}, semantic: asset.TextureColor}
|
|
}
|
|
}
|
|
sample.err = err
|
|
}
|
|
if sample.err != nil {
|
|
widgets.LabelWrapped(ui, sample.err.Error(), true)
|
|
return
|
|
}
|
|
width := min(float32(256), max(0, ui.Cursor().W))
|
|
if width <= 0 {
|
|
return
|
|
}
|
|
row := ui.Cursor().Next(width / 2)
|
|
area := mathx.Rect{X: row.X, Y: row.Y, W: width, H: row.H}
|
|
drawImageBackground(ui, area, 0)
|
|
if region := sample.display.region(p.ctx, sample.pixels, imageRGBA); !region.IsZero() {
|
|
ui.List().Texture(region, area, mathx.White)
|
|
}
|
|
ui.Outline(area, ui.Theme.Border)
|
|
ui.MouseOver(ui.ID("stroke-preview"), area)
|
|
widgets.Tooltip(ui, ui.ID("stroke-preview"), i18n.Tr("editor.art.stroke_preview_hint", "Example on an empty layer; selection, layer blending and locks are not shown."))
|
|
}
|