package ps import ( "encoding/json" "fmt" "log" "strings" "github.com/sbrow/ps/runner" ) // ArtLayer reflects some values from an Art Layer in a Photoshop document. // // TODO(sbrow): (2) Make TextLayer a subclass of ArtLayer. type ArtLayer struct { name string // The layer's name. bounds [2][2]int // The corners of the layer's bounding box. parent Group // The LayerSet/Document this layer is in. visible bool // Whether or not the layer is visible. current bool // Whether we've checked this layer since we loaded from disk. Color // The layer's color overlay effect (if any). *Stroke // The layer's stroke effect (if any). *TextItem // The layer's text, if it's a text layer. } // Bounds returns the coordinates of the corners of the ArtLayer's bounding box. func (a *ArtLayer) Bounds() [2][2]int { return a.bounds } // ArtLayerJSON is a bridge between the ArtLayer struct and // the encoding/json package, allowing ArtLayer's unexported fields // to ber written to and read from by the json package. type ArtLayerJSON struct { Name string Bounds [2][2]int Color [3]int Stroke [3]int StrokeAmt float32 Visible bool TextItem *TextItem } // MarshalJSON implements the json.Marshaler interface, allowing the ArtLayer to be // saved to disk in JSON format. func (a *ArtLayer) MarshalJSON() ([]byte, error) { return json.Marshal(&ArtLayerJSON{ Name: a.name, Bounds: a.bounds, Visible: a.visible, Color: a.Color.RGB(), Stroke: a.Stroke.RGB(), StrokeAmt: a.Stroke.Size, TextItem: a.TextItem, }) } // UnmarshalJSON loads json data into the object. func (a *ArtLayer) UnmarshalJSON(b []byte) error { tmp := &ArtLayerJSON{} if err := json.Unmarshal(b, &tmp); err != nil { return err } a.name = tmp.Name a.bounds = tmp.Bounds a.Color = RGB{tmp.Color[0], tmp.Color[1], tmp.Color[2]} a.Stroke = &Stroke{tmp.StrokeAmt, RGB{tmp.Stroke[0], tmp.Stroke[1], tmp.Stroke[2]}} a.visible = tmp.Visible a.current = false a.TextItem = tmp.TextItem if a.TextItem != nil { a.TextItem.parent = a } return nil } // Name returns the layer's name. func (a *ArtLayer) Name() string { return a.name } // Parent returns the Document or LayerSet this layer is contained in. func (a *ArtLayer) Parent() Group { return a.parent } // X1 returns the layer's leftmost x value. func (a *ArtLayer) X1() int { return a.bounds[0][0] } // X2 returns the layer's rightmost x value. func (a *ArtLayer) X2() int { return a.bounds[1][0] } // Y1 returns the layer's topmost y value. func (a *ArtLayer) Y1() int { return a.bounds[0][1] } // Y2 returns the layer's bottommost y value. func (a *ArtLayer) Y2() int { return a.bounds[1][1] } // SetParent sets Group c to be the group that holds this layer. func (a *ArtLayer) SetParent(c Group) { a.parent = c } // SetActive makes this layer active in Photoshop. // Layers need to be active to perform certain operations func (a *ArtLayer) SetActive() ([]byte, error) { js := fmt.Sprintf("app.activeDocument.activeLayer=%s", JSLayer(a.Path())) return DoJS("compilejs.jsx", js) } // SetColor creates a color overlay for the layer func (a *ArtLayer) SetColor(c Color) { if a.Color.RGB() == c.RGB() { if Mode == 2 || (Mode == 0 && a.current) { // log.Println("Skipping color: already set.") return } } if a.Stroke.Size != 0 { a.SetStroke(*a.Stroke, c) return } a.Color = c cols := a.Color.RGB() log.Printf(`Setting layer "%s" to color %v`, a.name, cols) r := cols[0] g := cols[1] b := cols[2] byt, err := a.SetActive() if len(byt) != 0 { log.Println(string(byt), "err") } if err != nil { log.Println(a.Path()) log.Panic(err) } byt, err = runner.Run("colorLayer", fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b)) if len(byt) != 0 { log.Println(string(byt), "err") } if err != nil { log.Panic(err) } } // SetStroke edits the "aura" around the layer. If a nil stroke is given, // The current stroke is removed. If a non-nil stroke is given and the // current stroke is nil, a stroke is added. func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { if stk.Size == 0 { a.Stroke = &stk a.SetColor(fill) return } if fill == nil { fill = a.Color } if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Stroke.Color.RGB() { if a.Color.RGB() == fill.RGB() { if Mode == 2 || (Mode == 0 && a.current) { // log.Println("Skipping stroke: already set.") return } } } byt, err := a.SetActive() if len(byt) != 0 { log.Println(string(byt)) } if err != nil { log.Panic(err) } a.Stroke = &stk a.Color = fill stkCol := stk.Color.RGB() col := fill.RGB() log.Printf("Setting layer %s stroke to %.2fpt %v and color to %v\n", a.name, a.Stroke.Size, a.Stroke.Color.RGB(), a.Color.RGB()) byt, err = runner.Run("colorStroke", fmt.Sprint(col[0]), fmt.Sprint(col[1]), fmt.Sprint(col[2]), fmt.Sprintf("%.2f", stk.Size), fmt.Sprint(stkCol[0]), fmt.Sprint(stkCol[1]), fmt.Sprint(stkCol[2])) if len(byt) != 0 { log.Println(string(byt)) } if err != nil { log.Panic(err) } } // Path returns the Path to this layer, through all of its parents. func (a *ArtLayer) Path() string { return fmt.Sprintf("%s%s", a.parent.Path(), a.name) } // SetVisible makes the layer visible. func (a *ArtLayer) SetVisible(b bool) error { if a.visible == b { return nil } a.visible = b switch b { case true: log.Printf("Showing %s", a.name) case false: log.Printf("Hiding %s", a.name) } js := fmt.Sprintf("%s.visible=%v;", strings.TrimRight(JSLayer(a.Path()), ";"), b) if byt, err := DoJS("compilejs.jsx", js); err != nil { log.Println(string(byt)) return err } return nil } // Visible returns whether or not the layer is currently hidden. func (a *ArtLayer) Visible() bool { return a.visible } // SetPos snaps the given layer boundry to the given point. // Valid options for bound are: "TL", "TR", "BL", "BR" func (a *ArtLayer) SetPos(x, y int, bound string) { if !a.visible || (x == 0 && y == 0) { return } var lyrX, lyrY int switch bound[:1] { case "B": lyrY = a.Y2() case "T": fallthrough default: lyrY = a.Y1() } switch bound[1:] { case "R": lyrX = a.X2() case "L": fallthrough default: lyrX = a.X1() } byt, err := DoJS("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY), ) if err != nil { fmt.Printf("%+v %+v\n", a.Parent(), a.Path()) panic(err) } var lyr ArtLayer err = json.Unmarshal(byt, &lyr) if err != nil { log.Panic(err) } a.bounds = lyr.bounds } // Refresh syncs the layer with Photoshop. func (a *ArtLayer) Refresh() error { var tmp *ArtLayer data, err := DoJS("getLayer.jsx", JSLayer(a.Path())) if err != nil && len(err.Error()) > 0 { return err } if err = json.Unmarshal(data, &tmp); err != nil { fmt.Println(string(data)) return err } tmp.SetParent(a.Parent()) a.name = tmp.name a.bounds = tmp.bounds a.TextItem = tmp.TextItem if a.TextItem != nil { a.TextItem.parent = a } a.parent = tmp.Parent() a.visible = tmp.visible a.current = true return nil }