From 5b36b9193a09632badc2c8ce990b410480c756aa Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 22 Mar 2018 20:11:51 -0400 Subject: [PATCH] Can now save and load from json Much improved speed over loading everything manually. --- .gitignore | 2 + colors.go | 57 +++++++++ ps.go | 1 - ps_test.go | 41 +++++-- scripts/activeDocName.jsx | 2 + scripts/getLayer.jsx | 4 +- scripts/isVisible.jsx | 2 + structs.go | 245 ++++++++++++++++++++++++++------------ 8 files changed, 264 insertions(+), 90 deletions(-) create mode 100644 .gitignore create mode 100644 colors.go create mode 100644 scripts/activeDocName.jsx create mode 100644 scripts/isVisible.jsx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6152548 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +data/ diff --git a/colors.go b/colors.go new file mode 100644 index 0000000..c371a05 --- /dev/null +++ b/colors.go @@ -0,0 +1,57 @@ +package ps + +import ( + "encoding/hex" +) + +var Colors map[string]Color = map[string]Color{ + "Gray": &RGB{128, 128, 128}, + "White": &RGB{255, 255, 255}, +} + +// Color represents a color. +type Color interface { + RGB() [3]int +} + +func Compare(a, b Color) Color { + A := a.RGB() + B := b.RGB() + Aavg := (A[0] + A[1] + A[2]) / 3 + Bavg := (B[0] + B[1] + B[2]) / 3 + if Aavg > Bavg { + return a + } + return b +} + +// Color is a color in RGB format. +type RGB struct { + Red int + Green int + Blue int +} + +// RGB returns the color in RGB format. +func (r RGB) RGB() [3]int { + return [3]int{r.Red, r.Green, r.Blue} +} + +// Hex is a color in hexidecimal format. +type Hex []uint8 + +func (h Hex) RGB() [3]int { + src := []byte(h) + dst := make([]byte, hex.DecodedLen(len(src))) + _, err := hex.Decode(dst, src) + if err != nil { + panic(err) + } + return [3]int{int(dst[0]), int(dst[1]), int(dst[2])} +} + +// Stroke represents a layer stroke effect. +type Stroke struct { + Size float32 + Color +} diff --git a/ps.go b/ps.go index dc30302..406b65b 100644 --- a/ps.go +++ b/ps.go @@ -184,7 +184,6 @@ func ApplyDataset(name string) ([]byte, error) { func JSLayer(path string) string { path = strings.TrimLeft(path, "/") pth := strings.Split(path, "/") - // fmt.Println(path) js := "app.activeDocument" last := len(pth) - 1 if last > 0 { diff --git a/ps_test.go b/ps_test.go index 53910a1..34e293b 100644 --- a/ps_test.go +++ b/ps_test.go @@ -1,7 +1,9 @@ package ps import ( + "encoding/json" "fmt" + "io/ioutil" "os" "path/filepath" "testing" @@ -112,9 +114,8 @@ func TestSaveAs(t *testing.T) { os.Remove("F:\\TEMP\\test.png") } -/* func TestLayerSet(t *testing.T) { - _, err := NewLayerSet("Areas/TitleBackground/") + _, err := NewLayerSet("Areas/TitleBackground/", nil) if err != nil { t.Fatal(err) } @@ -132,9 +133,9 @@ func TestMove(t *testing.T) { if err != nil { t.Fatal(err) } - lyr.Position(100, 50, "top") + lyr.SetPos(100, 50, "TL") } -*/ + func TestActiveDocument(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestDocument\"") @@ -166,10 +167,8 @@ func TestActiveDocument(t *testing.T) { } s := Stroke{Size: 4, Color: &RGB{0, 0, 0}} lyr.SetStroke(s, &RGB{128, 128, 128}) - } -/* func TestColor(t *testing.T) { byt, err := run("colorLayer.vbs", "255", "255", "255") fmt.Println(string(byt)) @@ -179,9 +178,7 @@ func TestColor(t *testing.T) { t.Fatal() } } -*/ -/* func TestApplyDataset(t *testing.T) { out := []byte("done!\r\n") ret, err := ApplyDataset(" Anger") @@ -215,20 +212,40 @@ func TestDocumentLayerSet(t *testing.T) { fmt.Println(lyr.name) } } -*/ -/* + +func TestLoadedDoc(t *testing.T) { + var d *Document + byt, err := ioutil.ReadFile("Document.txt") + if err != nil { + t.Fatal(err) + } + err = json.Unmarshal(byt, &d) + if err != nil { + t.Fatal(err) + } + if d != d.ArtLayers()[0].Parent() { + t.Fatal("Loaded document's ArtLayers do not point to doc") + } + if d != d.LayerSets()[0].Parent() { + t.Fatal("Loaded document's LayerSets do not point to doc") + } + if d.LayerSets()[0] != d.layerSets[0].artLayers[0].Parent() { + t.Fatal("Loaded document's LayerSet's ArtLayers do not point to layerSets") + } +} + func TestDoJs_HideLayer(t *testing.T) { err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { t.Fatal(err) } - lyr, err := NewLayerSet("Areas/TitleBackground") + lyr, err := NewLayerSet("Areas/TitleBackground", nil) lyr.SetVisible(false) if err != nil { t.Fatal(err) } } -*/ + func BenchmarkDoc_Go(b *testing.B) { for i := 0; i < b.N; i++ { _, err := ActiveDocument() diff --git a/scripts/activeDocName.jsx b/scripts/activeDocName.jsx new file mode 100644 index 0000000..ef8543b --- /dev/null +++ b/scripts/activeDocName.jsx @@ -0,0 +1,2 @@ +#include lib.js +var stdout = newFile(arguments[0]);stdout.writeln(app.activeDocument.name);stdout.close(); \ No newline at end of file diff --git a/scripts/getLayer.jsx b/scripts/getLayer.jsx index c42144a..ac439ea 100644 --- a/scripts/getLayer.jsx +++ b/scripts/getLayer.jsx @@ -2,7 +2,7 @@ var stdout = newFile(arguments[0]); var lyr = eval(arguments[1]); -stdout.writeln(('{"Name":"' + lyr.name + '", "Bounds": [[' + lyr.bounds[0] + ',' + +stdout.writeln(('{"Name":"' + lyr.name + '","Bounds":[[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + - lyr.bounds[3] + ']], "Visible": ' + lyr.visible + '}').replace(/ px/g, "")); + lyr.bounds[3] + ']],"Visible":' + lyr.visible + '}').replace(/ px/g, "")); stdout.close(); \ No newline at end of file diff --git a/scripts/isVisible.jsx b/scripts/isVisible.jsx new file mode 100644 index 0000000..0e926a8 --- /dev/null +++ b/scripts/isVisible.jsx @@ -0,0 +1,2 @@ +#include lib.js +var stdout = newFile(arguments[0]);stdout.writeln(eval(arguments[1]).visible);stdout.close(); \ No newline at end of file diff --git a/structs.go b/structs.go index ab960a7..3d4eead 100644 --- a/structs.go +++ b/structs.go @@ -1,65 +1,31 @@ package ps import ( - "encoding/hex" "encoding/json" "flag" "fmt" + "io/ioutil" "log" + "os" + "path/filepath" + "runtime" "strings" ) -var Colors map[string]Color = map[string]Color{ - "Gray": &RGB{128, 128, 128}, - "White": &RGB{255, 255, 255}, -} +type ModeEnum int -// Color represents a color. -type Color interface { - RGB() [3]int -} +// Mode determines how aggressively ps will attempt to sync with Photoshop. +var Mode ModeEnum -func Compare(a, b Color) Color { - A := a.RGB() - B := b.RGB() - Aavg := (A[0] + A[1] + A[2]) / 3 - Bavg := (B[0] + B[1] + B[2]) / 3 - if Aavg > Bavg { - return a - } - return b -} +// Normal Mode Always checks to see if layers are updated +// before returning them. +const Normal ModeEnum = 0 -// Color is a color in RGB format. -type RGB struct { - Red int - Green int - Blue int -} +// Safe Mode Always loads the document from scratch. (Slow) +const Safe ModeEnum = 1 -// RGB returns the color in RGB format. -func (r *RGB) RGB() [3]int { - return [3]int{r.Red, r.Green, r.Blue} -} - -// Hex is a color in hexidecimal format. -type Hex []uint8 - -func (h Hex) RGB() [3]int { - src := []byte(h) - dst := make([]byte, hex.DecodedLen(len(src))) - _, err := hex.Decode(dst, src) - if err != nil { - panic(err) - } - return [3]int{int(dst[0]), int(dst[1]), int(dst[2])} -} - -// Stroke represents a layer stroke effect. -type Stroke struct { - Size float32 - Color -} +// Fast mode never checks layers before returning. +const Fast ModeEnum = 2 // Group represents a Document or LayerSet. type Group interface { @@ -69,6 +35,8 @@ type Group interface { Path() string ArtLayers() []*ArtLayer LayerSets() []*LayerSet + MarshalJSON() ([]byte, error) + UnmarshalJSON(b []byte) error } // Document represents a Photoshop document (PSD file). @@ -88,6 +56,11 @@ type DocumentJSON struct { LayerSets []*LayerSet } +func (d *Document) MarshalJSON() ([]byte, error) { + return json.Marshal(&DocumentJSON{Name: d.name, Height: d.height, + Width: d.width, ArtLayers: d.artLayers, LayerSets: d.layerSets}) +} + func (d *Document) UnmarshalJSON(b []byte) error { tmp := &DocumentJSON{} if err := json.Unmarshal(b, &tmp); err != nil { @@ -97,7 +70,13 @@ func (d *Document) UnmarshalJSON(b []byte) error { d.height = tmp.Height d.width = tmp.Width d.artLayers = tmp.ArtLayers + for _, lyr := range d.artLayers { + lyr.SetParent(d) + } d.layerSets = tmp.LayerSets + for _, set := range d.layerSets { + set.SetParent(d) + } return nil } @@ -141,10 +120,37 @@ func (d *Document) Path() string { return "" } +// Filename returns the path to the json file for this document. +func (d *Document) Filename() string { + _, dir, _, ok := runtime.Caller(0) + if !ok { + log.Panic("No caller information") + } + return filepath.Join(filepath.Dir(dir), "data", + strings.TrimRight(string(d.name), "\r\n")+".txt") +} + func ActiveDocument() (*Document, error) { - log.Println("Loading ActiveDoucment/") - byt, err := DoJs("getActiveDoc.jsx") - var d *Document + log.Println("Loading ActiveDoucment") + d := &Document{} + + byt, err := DoJs("activeDocName.jsx") + if err != nil { + return nil, err + } + d.name = string(byt) + if Mode != 1 { + byt, err = ioutil.ReadFile(d.Filename()) + if err == nil { + log.Println("Previous version found, loading") + err = json.Unmarshal(byt, &d) + if err == nil { + return d, err + } + } + } + log.Println("Loading manually (This could take awhile)") + byt, err = DoJs("getActiveDoc.jsx") err = json.Unmarshal(byt, &d) for _, lyr := range d.artLayers { lyr.SetParent(d) @@ -155,27 +161,55 @@ func ActiveDocument() (*Document, error) { log.Fatal(err) } d.layerSets[i] = s - // s.SetParent(d) + s.SetParent(d) } + d.Dump() return d, err } +func (d *Document) Dump() { + f, err := os.Create(d.Filename()) + if err != nil { + log.Fatal(err) + } + defer f.Close() + byt, err := json.MarshalIndent(d, "", "\t") + if err != nil { + log.Fatal(err) + } + f.Write(byt) +} + // ArtLayer represents an Art Layer in a photoshop document. type ArtLayer struct { - name string + name string // The layer's name. // TextItem string - bounds [2][2]int - parent Group - visible bool - Color - *Stroke + bounds [2][2]int // The layers' corners. + 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. + *Stroke // The layer's stroke. } type ArtLayerJSON struct { - Name string - Bounds [2][2]int - Parent Group - Visible bool + Name string + Bounds [2][2]int + Visible bool + Color [3]int + Stroke [3]int + StrokeAmt float32 +} + +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, + }) } func (a *ArtLayer) UnmarshalJSON(b []byte) error { @@ -185,8 +219,10 @@ func (a *ArtLayer) UnmarshalJSON(b []byte) error { } a.name = tmp.Name a.bounds = tmp.Bounds - a.parent = tmp.Parent + 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 return nil } @@ -231,20 +267,25 @@ func (a *ArtLayer) SetActive() ([]byte, error) { // SetColor creates a color overlay for the layer func (a *ArtLayer) SetColor(c Color) { + if Mode == 2 && a.Color.RGB() == c.RGB() { + return + } + if a.Stroke.Size != 0 { + a.SetStroke(*a.Stroke, c) + return + } a.Color = c - cols := c.RGB() + cols := a.Color.RGB() + log.Printf(`Setting layer "%s" to color %v`, a.name, cols) r := cols[0] g := cols[1] b := cols[2] - if a.Stroke != nil { - a.SetStroke(Stroke{a.Stroke.Size, a.Stroke.Color}, a.Color) - return - } 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 = run("colorLayer", fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b)) @@ -257,6 +298,13 @@ func (a *ArtLayer) SetColor(c Color) { } func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { + if Mode == 2 { + if stk.Size == a.Stroke.Size && stk.Color.RGB() == a.Color.RGB() { + if a.Color.RGB() == fill.RGB() { + return + } + } + } byt, err := a.SetActive() if len(byt) != 0 { log.Println(string(byt)) @@ -264,8 +312,12 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { 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 = 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 { @@ -274,7 +326,6 @@ func (a *ArtLayer) SetStroke(stk Stroke, fill Color) { if err != nil { log.Panic(err) } - } func (a *ArtLayer) Parent() Group { @@ -299,9 +350,16 @@ func Layer(path string) (ArtLayer, error) { // SetVisible makes the layer visible. func (a *ArtLayer) SetVisible(b bool) { + if a.Visible() == b { + return + } + if b { + log.Printf("Showing %s", a.name) + } else { + log.Printf("Hiding %s", a.name) + } js := fmt.Sprintf("%s.visible=%v;", strings.TrimRight(JSLayer(a.Path()), ";"), b) - log.Printf("Setting %s.Visible to %v\n", a.name, b) DoJs("compilejs.jsx", js) } @@ -314,6 +372,9 @@ func (a *ArtLayer) Visible() bool { // Valid options for bound are: TL, TR, BL, BR // TODO: Improve func (a *ArtLayer) SetPos(x, y int, bound string) { + if x == 0 && y == 0 { + return + } if !a.visible { return } @@ -326,35 +387,51 @@ func (a *ArtLayer) SetPos(x, y int, bound string) { lyrY = a.Y1() } byt, err := DoJs("moveLayer.jsx", JSLayer(a.Path()), fmt.Sprint(x-lyrX), fmt.Sprint(y-lyrY)) + var bounds [2][2]int if err != nil { panic(err) } - json.Unmarshal(byt, &a) + json.Unmarshal(byt, bounds) + a.bounds = bounds } type LayerSet struct { name string parent Group + current bool // Whether we've checked this layer since we loaded from disk. artLayers []*ArtLayer layerSets []*LayerSet } type LayerSetJSON struct { Name string - Parent Group ArtLayers []*ArtLayer LayerSets []*LayerSet } +func (l *LayerSet) MarshalJSON() ([]byte, error) { + return json.Marshal(&LayerSetJSON{ + Name: l.name, + ArtLayers: l.artLayers, + LayerSets: l.layerSets, + }) +} + func (l *LayerSet) UnmarshalJSON(b []byte) error { tmp := &LayerSetJSON{} if err := json.Unmarshal(b, &tmp); err != nil { return err } l.name = tmp.Name - l.parent = tmp.Parent l.artLayers = tmp.ArtLayers + for _, lyr := range l.artLayers { + lyr.SetParent(l) + } l.layerSets = tmp.LayerSets + for _, set := range l.layerSets { + set.SetParent(l) + } + l.current = false return nil } @@ -363,6 +440,11 @@ func (l *LayerSet) Name() string { } func (l *LayerSet) ArtLayers() []*ArtLayer { + for i := 0; i < len(l.artLayers); i++ { + if l.artLayers[i] != nil && !l.artLayers[i].current { + l.artLayers[i] = l.ArtLayer(l.artLayers[i].name) + } + } return l.artLayers } @@ -371,6 +453,18 @@ func (l *LayerSet) ArtLayers() []*ArtLayer { func (l *LayerSet) ArtLayer(name string) *ArtLayer { for _, lyr := range l.artLayers { if lyr.name == name { + if Mode == 0 && !lyr.current { + byt, err := DoJs("getLayer.jsx", JSLayer(lyr.Path())) + if err != nil { + log.Panic(err) + } + var lyr2 *ArtLayer + err = json.Unmarshal(byt, &lyr2) + lyr.name = lyr2.name + lyr.bounds = lyr2.bounds + lyr.visible = lyr2.visible + lyr.current = true + } return lyr } } @@ -408,11 +502,13 @@ func (l *LayerSet) Path() string { } func NewLayerSet(path string, g Group) (*LayerSet, error) { + path = strings.Replace(path, "//", "/", -1) byt, err := DoJs("getLayerSet.jsx", JSLayer(path)) var out *LayerSet err = json.Unmarshal(byt, &out) if flag.Lookup("test.v") != nil { - // log.Println(string(byt)) + // log.Println(path) + // log.Println(out) } out.SetParent(g) log.Printf("Loading ActiveDocument/%s\n", out.Path()) @@ -423,7 +519,6 @@ func NewLayerSet(path string, g Group) (*LayerSet, error) { lyr.SetParent(out) } for i, set := range out.layerSets { - // log.Println("\t", set.name) s, err := NewLayerSet(fmt.Sprintf("%s%s/", path, set.Name()), out) if err != nil { log.Fatal(err)