diff --git a/NOTICE.txt b/NOTICE.txt index 0316f7026a..9a24a1304a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -4132,3 +4132,76 @@ A caching, resizing image proxy written in Go of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +--- + +## oov/psd + +This product contains 'psd' by oov. + +A PSD/PSB file reader for go + +* HOMEPAGE: + * https://github.com/oov/psd + +* LICENSE: MIT + +MIT License + +Copyright (c) 2016 oov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +## gopherjs + +This product contains 'gopherjs' by Richard Musiol. + +A Go code to javascript code compiler. + +* HOMEPAGE: + * https://github.com/gopherjs/gopherjs + +* LICENSE: + +Copyright (c) 2013 Richard Musiol. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/app/file.go b/app/file.go index 6371386b97..4b615ddc3a 100644 --- a/app/file.go +++ b/app/file.go @@ -24,6 +24,7 @@ import ( "time" "github.com/disintegration/imaging" + _ "github.com/oov/psd" "github.com/rwcarlsen/goexif/exif" _ "golang.org/x/image/bmp" _ "golang.org/x/image/tiff" diff --git a/go.mod b/go.mod index d80c868d0d..067fe1ec56 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,7 @@ require ( github.com/olivere/elastic v6.2.33+incompatible // indirect github.com/onsi/ginkgo v1.12.0 // indirect github.com/onsi/gomega v1.9.0 // indirect + github.com/oov/psd v0.0.0-20200705094106-99303fb2511f github.com/opentracing/opentracing-go v1.1.0 github.com/pborman/uuid v1.2.0 github.com/pelletier/go-toml v1.8.0 // indirect diff --git a/go.sum b/go.sum index 8f8c876b6c..cc4c561fbd 100644 --- a/go.sum +++ b/go.sum @@ -529,6 +529,8 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/oov/psd v0.0.0-20200705094106-99303fb2511f h1:slBBh1GLx3xo0xx0v4HT0pI02+UcOJXTYRlg6QtN60Y= +github.com/oov/psd v0.0.0-20200705094106-99303fb2511f/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= diff --git a/vendor/github.com/gopherjs/gopherjs/LICENSE b/vendor/github.com/gopherjs/gopherjs/LICENSE new file mode 100644 index 0000000000..d496fef109 --- /dev/null +++ b/vendor/github.com/gopherjs/gopherjs/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013 Richard Musiol. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gopherjs/gopherjs/js/js.go b/vendor/github.com/gopherjs/gopherjs/js/js.go new file mode 100644 index 0000000000..3fbf1d88c6 --- /dev/null +++ b/vendor/github.com/gopherjs/gopherjs/js/js.go @@ -0,0 +1,168 @@ +// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax. +// +// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed: +// +// | Go type | JavaScript type | Conversions back to interface{} | +// | --------------------- | --------------------- | ------------------------------- | +// | bool | Boolean | bool | +// | integers and floats | Number | float64 | +// | string | String | string | +// | []int8 | Int8Array | []int8 | +// | []int16 | Int16Array | []int16 | +// | []int32, []int | Int32Array | []int | +// | []uint8 | Uint8Array | []uint8 | +// | []uint16 | Uint16Array | []uint16 | +// | []uint32, []uint | Uint32Array | []uint | +// | []float32 | Float32Array | []float32 | +// | []float64 | Float64Array | []float64 | +// | all other slices | Array | []interface{} | +// | arrays | see slice type | see slice type | +// | functions | Function | func(...interface{}) *js.Object | +// | time.Time | Date | time.Time | +// | - | instanceof Node | *js.Object | +// | maps, structs | instanceof Object | map[string]interface{} | +// +// Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa. +package js + +// Object is a container for a native JavaScript object. Calls to its methods are treated specially by GopherJS and translated directly to their JavaScript syntax. A nil pointer to Object is equal to JavaScript's "null". Object can not be used as a map key. +type Object struct{ object *Object } + +// Get returns the object's property with the given key. +func (o *Object) Get(key string) *Object { return o.object.Get(key) } + +// Set assigns the value to the object's property with the given key. +func (o *Object) Set(key string, value interface{}) { o.object.Set(key, value) } + +// Delete removes the object's property with the given key. +func (o *Object) Delete(key string) { o.object.Delete(key) } + +// Length returns the object's "length" property, converted to int. +func (o *Object) Length() int { return o.object.Length() } + +// Index returns the i'th element of an array. +func (o *Object) Index(i int) *Object { return o.object.Index(i) } + +// SetIndex sets the i'th element of an array. +func (o *Object) SetIndex(i int, value interface{}) { o.object.SetIndex(i, value) } + +// Call calls the object's method with the given name. +func (o *Object) Call(name string, args ...interface{}) *Object { return o.object.Call(name, args...) } + +// Invoke calls the object itself. This will fail if it is not a function. +func (o *Object) Invoke(args ...interface{}) *Object { return o.object.Invoke(args...) } + +// New creates a new instance of this type object. This will fail if it not a function (constructor). +func (o *Object) New(args ...interface{}) *Object { return o.object.New(args...) } + +// Bool returns the object converted to bool according to JavaScript type conversions. +func (o *Object) Bool() bool { return o.object.Bool() } + +// String returns the object converted to string according to JavaScript type conversions. +func (o *Object) String() string { return o.object.String() } + +// Int returns the object converted to int according to JavaScript type conversions (parseInt). +func (o *Object) Int() int { return o.object.Int() } + +// Int64 returns the object converted to int64 according to JavaScript type conversions (parseInt). +func (o *Object) Int64() int64 { return o.object.Int64() } + +// Uint64 returns the object converted to uint64 according to JavaScript type conversions (parseInt). +func (o *Object) Uint64() uint64 { return o.object.Uint64() } + +// Float returns the object converted to float64 according to JavaScript type conversions (parseFloat). +func (o *Object) Float() float64 { return o.object.Float() } + +// Interface returns the object converted to interface{}. See table in package comment for details. +func (o *Object) Interface() interface{} { return o.object.Interface() } + +// Unsafe returns the object as an uintptr, which can be converted via unsafe.Pointer. Not intended for public use. +func (o *Object) Unsafe() uintptr { return o.object.Unsafe() } + +// Error encapsulates JavaScript errors. Those are turned into a Go panic and may be recovered, giving an *Error that holds the JavaScript error object. +type Error struct { + *Object +} + +// Error returns the message of the encapsulated JavaScript error object. +func (err *Error) Error() string { + return "JavaScript error: " + err.Get("message").String() +} + +// Stack returns the stack property of the encapsulated JavaScript error object. +func (err *Error) Stack() string { + return err.Get("stack").String() +} + +// Global gives JavaScript's global object ("window" for browsers and "GLOBAL" for Node.js). +var Global *Object + +// Module gives the value of the "module" variable set by Node.js. Hint: Set a module export with 'js.Module.Get("exports").Set("exportName", ...)'. +var Module *Object + +// Undefined gives the JavaScript value "undefined". +var Undefined *Object + +// Debugger gets compiled to JavaScript's "debugger;" statement. +func Debugger() {} + +// InternalObject returns the internal JavaScript object that represents i. Not intended for public use. +func InternalObject(i interface{}) *Object { + return nil +} + +// MakeFunc wraps a function and gives access to the values of JavaScript's "this" and "arguments" keywords. +func MakeFunc(fn func(this *Object, arguments []*Object) interface{}) *Object { + return Global.Call("$makeFunc", InternalObject(fn)) +} + +// Keys returns the keys of the given JavaScript object. +func Keys(o *Object) []string { + if o == nil || o == Undefined { + return nil + } + a := Global.Get("Object").Call("keys", o) + s := make([]string, a.Length()) + for i := 0; i < a.Length(); i++ { + s[i] = a.Index(i).String() + } + return s +} + +// MakeWrapper creates a JavaScript object which has wrappers for the exported methods of i. Use explicit getter and setter methods to expose struct fields to JavaScript. +func MakeWrapper(i interface{}) *Object { + v := InternalObject(i) + o := Global.Get("Object").New() + o.Set("__internal_object__", v) + methods := v.Get("constructor").Get("methods") + for i := 0; i < methods.Length(); i++ { + m := methods.Index(i) + if m.Get("pkg").String() != "" { // not exported + continue + } + o.Set(m.Get("name").String(), func(args ...*Object) *Object { + return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true).Call("apply", v, args) + }) + } + return o +} + +// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice. +func NewArrayBuffer(b []byte) *Object { + slice := InternalObject(b) + offset := slice.Get("$offset").Int() + length := slice.Get("$length").Int() + return slice.Get("$array").Get("buffer").Call("slice", offset, offset+length) +} + +// M is a simple map type. It is intended as a shorthand for JavaScript objects (before conversion). +type M map[string]interface{} + +// S is a simple slice type. It is intended as a shorthand for JavaScript arrays (before conversion). +type S []interface{} + +func init() { + // avoid dead code elimination + e := Error{} + _ = e +} diff --git a/vendor/github.com/oov/psd/.gitignore b/vendor/github.com/oov/psd/.gitignore new file mode 100644 index 0000000000..ea1472ec1f --- /dev/null +++ b/vendor/github.com/oov/psd/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/vendor/github.com/oov/psd/README.md b/vendor/github.com/oov/psd/README.md new file mode 100644 index 0000000000..68720aa1ea --- /dev/null +++ b/vendor/github.com/oov/psd/README.md @@ -0,0 +1,149 @@ +# PSD/PSB(Photoshop) file reader for Go programming language + +It works almost well but it is still in development. + +## License + +MIT License + +Copyright (c) 2016 oov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## How to use + +### Example1 + +Simple psd -> png conversion. + +```go +package main + +import ( + "image" + "image/png" + "os" + + _ "github.com/oov/psd" +) + +func main() { + file, err := os.Open("image.psd") + if err != nil { + panic(err) + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + panic(err) + } + + out, err := os.Create("image.png") + if err != nil { + panic(err) + } + err = png.Encode(out, img) + if err != nil { + panic(err) + } +} +``` + +### Example2 + +Extract all layer images. + +```go +package main + +import ( + "fmt" + "image/png" + "os" + + "github.com/oov/psd" +) + +func processLayer(filename string, layerName string, l *psd.Layer) error { + if len(l.Layer) > 0 { + for i, ll := range l.Layer { + if err := processLayer( + fmt.Sprintf("%s_%03d", filename, i), + layerName+"/"+ll.Name, &ll); err != nil { + return err + } + } + } + if !l.HasImage() { + return nil + } + fmt.Printf("%s -> %s.png\n", layerName, filename) + out, err := os.Create(fmt.Sprintf("%s.png", filename)) + if err != nil { + return err + } + defer out.Close() + return png.Encode(out, l.Picker) +} + +func main() { + file, err := os.Open("image.psd") + if err != nil { + panic(err) + } + defer file.Close() + + img, _, err := psd.Decode(file, &psd.DecodeOptions{SkipMergedImage: true}) + if err != nil { + panic(err) + } + for i, layer := range img.Layer { + if err = processLayer(fmt.Sprintf("%03d", i), layer.Name, &layer); err != nil { + panic(err) + } + } +} +``` + +# Current status + +It is not implemented any blending functions because layer composition isn't covered by this package at present. + +- [Image Resource Section](http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_69883) is parsed but [Image resource IDs](http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_38034) are not defined as constant. +- [Global layer mask info](http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115) is not parsed. +- [Layer blending ranges data](http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_21332) is not parsed. +- [Additinal Layer Information](http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1049436) is parsed but keys are almost not defined as constant. + +## Color Modes + +### Implemented + +- Bitmap 1bit +- Grayscale 8bit +- Grayscale 16bit +- Grayscale 32bit +- Indexed +- RGB 8bit +- RGB 16bit +- RGB 32bit +- CMYK 8bit +- CMYK 16bit + +### Not implemented + +- CMYK 32bit +- Multichannel +- Duotone +- Lab + +## Supported Compression Methods + +- Raw +- RLE(PackBits) +- ZIP without prediction (not tested) +- ZIP with prediction diff --git a/vendor/github.com/oov/psd/blendmode.go b/vendor/github.com/oov/psd/blendmode.go new file mode 100644 index 0000000000..62f1f92a71 --- /dev/null +++ b/vendor/github.com/oov/psd/blendmode.go @@ -0,0 +1,103 @@ +package psd + +// BlendMode represents the blend mode. +type BlendMode string + +// These blend modes are defined in this document. +// +// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_13084 +const ( + BlendModePassThrough = BlendMode("pass") + BlendModeNormal = BlendMode("norm") + BlendModeDissolve = BlendMode("diss") + BlendModeDarken = BlendMode("dark") + BlendModeMultiply = BlendMode("mul ") + BlendModeColorBurn = BlendMode("idiv") + BlendModeLinearBurn = BlendMode("lbrn") + BlendModeDarkerColor = BlendMode("dkCl") + BlendModeLighten = BlendMode("lite") + BlendModeScreen = BlendMode("scrn") + BlendModeColorDodge = BlendMode("div ") + BlendModeLinearDodge = BlendMode("lddg") + BlendModeLighterColor = BlendMode("lgCl") + BlendModeOverlay = BlendMode("over") + BlendModeSoftLight = BlendMode("sLit") + BlendModeHardLight = BlendMode("hLit") + BlendModeVividLight = BlendMode("vLit") + BlendModeLinearLight = BlendMode("lLit") + BlendModePinLight = BlendMode("pLit") + BlendModeHardMix = BlendMode("hMix") + BlendModeDifference = BlendMode("diff") + BlendModeExclusion = BlendMode("smud") + BlendModeSubtract = BlendMode("fsub") + BlendModeDivide = BlendMode("fdiv") + BlendModeHue = BlendMode("hue ") + BlendModeSaturation = BlendMode("sat ") + BlendModeColor = BlendMode("colr") + BlendModeLuminosity = BlendMode("lum ") +) + +// String implements fmt.Stringer interface. +// +// The return value respects blend name that is described in "Compositing and Blending Level 1"(https://www.w3.org/TR/compositing-1/#blending). +func (bm BlendMode) String() string { + switch bm { + case BlendModePassThrough: + return "pass-through" + case BlendModeNormal: + return "normal" + case BlendModeDissolve: + return "dissolve" + case BlendModeDarken: + return "darken" + case BlendModeMultiply: + return "multiply" + case BlendModeColorBurn: + return "color-burn" + case BlendModeLinearBurn: + return "linear-burn" + case BlendModeDarkerColor: + return "darker-color" + case BlendModeLighten: + return "lighten" + case BlendModeScreen: + return "screen" + case BlendModeColorDodge: + return "color-dodge" + case BlendModeLinearDodge: + return "linear-dodge" + case BlendModeLighterColor: + return "lighter-color" + case BlendModeOverlay: + return "overlay" + case BlendModeSoftLight: + return "soft-light" + case BlendModeHardLight: + return "hard-light" + case BlendModeVividLight: + return "vivid-light" + case BlendModeLinearLight: + return "linear-light" + case BlendModePinLight: + return "pin-light" + case BlendModeHardMix: + return "hard-mix" + case BlendModeDifference: + return "difference" + case BlendModeExclusion: + return "exclusion" + case BlendModeSubtract: + return "subtract" + case BlendModeDivide: + return "divide" + case BlendModeHue: + return "hue" + case BlendModeSaturation: + return "saturation" + case BlendModeColor: + return "color" + case BlendModeLuminosity: + return "luminosity" + } + return "unknown-blend-name-" + string(bm) +} diff --git a/vendor/github.com/oov/psd/color/color.go b/vendor/github.com/oov/psd/color/color.go new file mode 100644 index 0000000000..95f762e6cd --- /dev/null +++ b/vendor/github.com/oov/psd/color/color.go @@ -0,0 +1,327 @@ +package color + +import ( + "image/color" + "math" +) + +func fromFloat(v, gamma float64) uint32 { + x := math.Pow(v, gamma) + switch { + case x >= 1: + return 0xffff + case x <= 0: + return 0 + default: + return uint32(x * 0xffff) + } +} + +func toFloat(v uint32, gamma float64) float64 { + return math.Pow(float64(v)/0xffff, gamma) +} + +// Gray1 represents an 1-bit monochrome bitmap color. +type Gray1 struct { + Y bool +} + +// RGBA implements color.Color interface's method. +func (c Gray1) RGBA() (r, g, b, a uint32) { + if c.Y { + return 0xffff, 0xffff, 0xffff, 0xffff + } + return 0, 0, 0, 0xffff +} + +func gray1Model(c color.Color) color.Color { + if _, ok := c.(Gray1); ok { + return c + } + r, g, b, _ := c.RGBA() + y := (299*r + 587*g + 114*b + 500) / 1000 + return Gray1{y >= 0x8000} +} + +// Gray32 represents a 32-bit float grayscale color. +type Gray32 struct { + Y float32 +} + +// RGBA implements color.Color interface's method. +func (c Gray32) RGBA() (r, g, b, a uint32) { + const gamma = 1.0 / 2.2 + y := fromFloat(float64(c.Y), gamma) + return y, y, y, 0xffff +} + +func gray32Model(c color.Color) color.Color { + if _, ok := c.(Gray32); ok { + return c + } + r, g, b, _ := c.RGBA() + y := (299*r + 587*g + 114*b + 500) / 1000 + const gamma = 2.2 + return Gray32{float32(toFloat(y, gamma))} +} + +type NGrayA struct { + Y uint8 + A uint8 +} + +// RGBA implements color.Color interface's method. +func (c NGrayA) RGBA() (uint32, uint32, uint32, uint32) { + y := uint32(c.Y) * 0x101 + if c.A == 0xff { + return y, y, y, 0xffff + } + if c.A == 0 { + return 0, 0, 0, 0 + } + a := uint32(c.A) * 0x101 + y = y * a / 0xffff + return y, y, y, a +} + +func nGrayAModel(c color.Color) color.Color { + if _, ok := c.(NGrayA64); ok { + return c + } + r, g, b, a := c.RGBA() + if a == 0 { + return NGrayA{0, 0} + } + y := (299*r + 587*g + 114*b + 500) / 1000 + if a == 0xffff { + return NGrayA{uint8(y >> 8), 0xff} + } + y = (y * 0xffff) / a + return NGrayA{uint8(y >> 8), uint8(a >> 8)} +} + +type NGrayA32 struct { + Y uint16 + A uint16 +} + +// RGBA implements color.Color interface's method. +func (c NGrayA32) RGBA() (uint32, uint32, uint32, uint32) { + y := uint32(c.Y) + if c.A == 0xffff { + return y, y, y, 0xffff + } + if c.A == 0 { + return 0, 0, 0, 0 + } + a := uint32(c.A) + y = y * a / 0xffff + return y, y, y, a +} + +func nGrayA32Model(c color.Color) color.Color { + if _, ok := c.(NGrayA64); ok { + return c + } + r, g, b, a := c.RGBA() + if a == 0 { + return NGrayA32{0, 0} + } + y := (299*r + 587*g + 114*b + 500) / 1000 + if a == 0xffff { + return NGrayA32{uint16(y), 0xffff} + } + y = (y * 0xffff) / a + return NGrayA32{uint16(y), uint16(a)} +} + +type NGrayA64 struct { + Y float32 + A float32 +} + +// RGBA implements color.Color interface's method. +func (c NGrayA64) RGBA() (uint32, uint32, uint32, uint32) { + y := fromFloat(float64(c.Y), 1.0/2.2) + switch { + case c.A >= 1: + return y, y, y, 0xffff + case c.A <= 0: + return 0, 0, 0, 0 + } + a := uint32(c.A * 0xffff) + y = y * a / 0xffff + return y, y, y, a +} + +func nGrayA64Model(c color.Color) color.Color { + if _, ok := c.(NGrayA64); ok { + return c + } + r, g, b, a := c.RGBA() + if a == 0 { + return NGrayA64{0, 0} + } + y := (299*r + 587*g + 114*b + 500) / 1000 + x := float32(toFloat(y, 2.2)) + if a == 0xffff { + return NGrayA64{x, 1} + } + xa := float32(a) / 0xffff + return NGrayA64{x / xa, xa} +} + +type NRGBA128 struct { + R, G, B, A float32 +} + +// RGBA implements color.Color interface's method. +func (c NRGBA128) RGBA() (uint32, uint32, uint32, uint32) { + const gamma = 1.0 / 2.2 + r := fromFloat(float64(c.R), gamma) + g := fromFloat(float64(c.G), gamma) + b := fromFloat(float64(c.B), gamma) + switch { + case c.A >= 1: + return r, g, b, 0xffff + case c.A <= 0: + return 0, 0, 0, 0 + } + a := uint32(c.A * 0xffff) + r = r * a / 0xffff + g = g * a / 0xffff + b = b * a / 0xffff + return r, g, b, a +} + +func nRGBA128Model(c color.Color) color.Color { + if _, ok := c.(NRGBA128); ok { + return c + } + r, g, b, a := c.RGBA() + const gamma = 2.2 + fr := float32(toFloat(r, gamma)) + fg := float32(toFloat(g, gamma)) + fb := float32(toFloat(b, gamma)) + switch { + case a >= 0xffff: + return NRGBA128{fr, fg, fb, 1} + case a == 0: + return NRGBA128{} + } + fa := 0xffff / float32(a) + fr *= fa + fg *= fa + fb *= fa + return NRGBA128{fr, fg, fb, float32(a) / 0xffff} +} + +// NCMYKA represents a non-alpha-premultiplied CMYK color, having 8 bits for each of cyan, +// magenta, yellow, black and alpha. +// NCMYKA is different from color.CMYK, CMYK is inverted value. +// +// It is not associated with any particular color profile. +type NCMYKA struct { + C, M, Y, K, A uint8 +} + +// RGBA implements color.Color interface's method. +func (c NCMYKA) RGBA() (uint32, uint32, uint32, uint32) { + w := uint32(c.K) * 0x10201 + r := uint32(c.C) * w / 0xffff + g := uint32(c.M) * w / 0xffff + b := uint32(c.Y) * w / 0xffff + if c.A == 0xff { + return r, g, b, 0xffff + } + if c.A == 0 { + return 0, 0, 0, 0 + } + + a := uint32(c.A) * 0x101 + r = r * a / 0xffff + g = g * a / 0xffff + b = b * a / 0xffff + return r, g, b, a +} + +func nCMYKAModel(c color.Color) color.Color { + if _, ok := c.(NCMYKA); ok { + return c + } + r, g, b, a := c.RGBA() + cc, mm, yy, kk := color.RGBToCMYK(uint8(r>>8), uint8(g>>8), uint8(b>>8)) + cc = uint8((uint32(cc) * 0xffff) / a) + mm = uint8((uint32(mm) * 0xffff) / a) + yy = uint8((uint32(yy) * 0xffff) / a) + kk = uint8((uint32(kk) * 0xffff) / a) + return NCMYKA{255 - cc, 255 - mm, 255 - yy, 255 - kk, uint8(a >> 8)} +} + +type NCMYKA80 struct { + C, M, Y, K, A uint16 +} + +// RGBA implements color.Color interface's method. +func (c NCMYKA80) RGBA() (uint32, uint32, uint32, uint32) { + w := uint32(c.K) + r := uint32(c.C) * w / 0xffff + g := uint32(c.M) * w / 0xffff + b := uint32(c.Y) * w / 0xffff + if c.A == 0xffff { + return r, g, b, 0xffff + } + if c.A == 0 { + return 0, 0, 0, 0 + } + + a := uint32(c.A) + r = r * a / 0xffff + g = g * a / 0xffff + b = b * a / 0xffff + return r, g, b, a +} + +func nCMYKA80Model(c color.Color) color.Color { + if _, ok := c.(NCMYKA); ok { + return c + } + r, g, b, a := c.RGBA() + if a == 0 { + return NCMYKA80{0xffff, 0xffff, 0xffff, 0xffff, 0} + } + w := r + if w < g { + w = g + } + if w < b { + w = b + } + if w == 0 { + return NCMYKA80{0xffff, 0xffff, 0xffff, 0xffff, uint16(a)} + } + cc := (w - r) * 0xffff / w + mm := (w - g) * 0xffff / w + yy := (w - b) * 0xffff / w + kk := 0xffff - w + if a == 0xffff { + return NCMYKA80{uint16(0xffff - cc), uint16(0xffff - mm), uint16(0xffff - yy), uint16(0xffff - kk), 0xffff} + } + cc = (cc * 0xffff) / a + mm = (mm * 0xffff) / a + yy = (yy * 0xffff) / a + kk = (kk * 0xffff) / a + return NCMYKA80{uint16(0xffff - cc), uint16(0xffff - mm), uint16(0xffff - yy), uint16(0xffff - kk), uint16(a)} +} + +// These are color model. +var ( + Gray1Model = color.ModelFunc(gray1Model) + NGrayAModel = color.ModelFunc(nGrayAModel) + Gray32Model = color.ModelFunc(gray32Model) + NGrayA32Model = color.ModelFunc(nGrayA32Model) + NGrayA64Model = color.ModelFunc(nGrayA64Model) + NRGBA128Model = color.ModelFunc(nRGBA128Model) + NCMYKAModel = color.ModelFunc(nCMYKAModel) + NCMYKA80Model = color.ModelFunc(nCMYKA80Model) +) diff --git a/vendor/github.com/oov/psd/colormode.go b/vendor/github.com/oov/psd/colormode.go new file mode 100644 index 0000000000..8e4e9ed54e --- /dev/null +++ b/vendor/github.com/oov/psd/colormode.go @@ -0,0 +1,34 @@ +package psd + +// ColorMode represents color mode that is used in psd file. +type ColorMode int16 + +// These color modes are defined in this document. +// +// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_19840 +const ( + ColorModeBitmap = ColorMode(0) + ColorModeGrayscale = ColorMode(1) + ColorModeIndexed = ColorMode(2) + ColorModeRGB = ColorMode(3) + ColorModeCMYK = ColorMode(4) + ColorModeMultichannel = ColorMode(7) + ColorModeDuotone = ColorMode(8) + ColorModeLab = ColorMode(9) +) + +// Channels returns the number of channels for the color mode. +// The return value is not including alpha channel. +func (c ColorMode) Channels() int { + switch c { + case ColorModeBitmap, + ColorModeGrayscale, + ColorModeIndexed: + return 1 + case ColorModeRGB: + return 3 + case ColorModeCMYK: + return 4 + } + return -1 +} diff --git a/vendor/github.com/oov/psd/compress.go b/vendor/github.com/oov/psd/compress.go new file mode 100644 index 0000000000..bc66980af9 --- /dev/null +++ b/vendor/github.com/oov/psd/compress.go @@ -0,0 +1,90 @@ +package psd + +import ( + "compress/zlib" + "errors" + "image" + "io" +) + +// CompressionMethod represents compression method that is used in psd file. +type CompressionMethod int16 + +// These compression methods are defined in this document. +// +// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1054855 +const ( + CompressionMethodRaw = CompressionMethod(0) + CompressionMethodRLE = CompressionMethod(1) + CompressionMethodZIPWithoutPrediction = CompressionMethod(2) + CompressionMethodZIPWithPrediction = CompressionMethod(3) +) + +// Decode decodes the compressed image data from r. +// +// You can pass 0 to sizeHint if unknown, but in this case may read more data than necessary from r. +func (cm CompressionMethod) Decode(dest []byte, r io.Reader, sizeHint int64, rect image.Rectangle, depth int, channels int, large bool) (read int, err error) { + switch cm { + case CompressionMethodRaw: + return io.ReadFull(r, dest) + case CompressionMethodRLE: + return decodePackBits(dest, r, rect.Dx(), rect.Dy()*channels, large) + case CompressionMethodZIPWithoutPrediction: + return decodeZLIB(dest, r, sizeHint) + case CompressionMethodZIPWithPrediction: + if read, err = decodeZLIB(dest, r, sizeHint); err != nil { + return read, err + } + decodeDelta(dest, rect.Dx(), depth) + return read, err + } + return 0, errors.New("psd: compression method " + itoa(int(cm)) + " is not implemented") +} + +// decodeZLIB decodes compressed data by zlib. +// You can pass 0 to length if unknown, +// but in that case may read more data than necessary from r. +func decodeZLIB(dest []byte, r io.Reader, length int64) (read int, err error) { + N := length + if N == 0 { + N = 0x7fffffffffffffff + } + lr := &io.LimitedReader{R: r, N: N} + zr, err := zlib.NewReader(lr) + if err != nil { + return 0, err + } + if _, err = io.ReadFull(zr, dest); err != nil { + zr.Close() + return int(N - lr.N), err + } + if err = zr.Close(); err != nil { + return int(N - lr.N), err + } + return int(N - lr.N), nil +} + +func decodeDelta(buf []byte, width int, depth int) { + switch depth { + case 16: + var d uint16 + for i := 0; i < len(buf); { + d = 0 + for j := 0; j < width; j++ { + d += readUint16(buf, i) + writeUint16(buf, d, i) + i += 2 + } + } + case 32: + var d uint32 + for i := 0; i < len(buf); { + d = 0 + for j := 0; j < width; j++ { + d += readUint32(buf, i) + writeUint32(buf, d, i) + i += 4 + } + } + } +} diff --git a/vendor/github.com/oov/psd/compress.inc.js b/vendor/github.com/oov/psd/compress.inc.js new file mode 100644 index 0000000000..7072d457b1 --- /dev/null +++ b/vendor/github.com/oov/psd/compress.inc.js @@ -0,0 +1,41 @@ +$global['$github.com/oov/psd$'] = { + decodePackBitsLines: function(buf, lines, large) { + var r = new Uint32Array(lines); + var lens = new DataView(buf.buffer); + if (large) { + for (var i = 0; i < lines; ++i) { + r[i] = lens.getUint32(i << 2, false); + } + } else { + for (var i = 0; i < lines; ++i) { + r[i] = lens.getUint16(i << 1, false); + } + } + return r; + }, + decodePackBits: function(dest, buf, lens) { + var ofs = 0, + d = 0; + for (var i = 0; i < lens.length; ++i) { + for (var j = 0, ln = lens[i]; j < ln;) { + if (buf[ofs] <= 0x7f) { + var l = buf[ofs++] + 1; + for (var k = 0; k < l; ++k) { + dest[d++] = buf[ofs++]; + } + j += l + 1; + continue; + } + if (buf[ofs] == 0x80) { + ofs++; + continue; + } + for (var k = 0, l = 256 - buf[ofs++] + 1, c = buf[ofs++]; k < l; ++k) { + dest[d++] = c; + } + j += 2; + } + } + return true; + } +} diff --git a/vendor/github.com/oov/psd/compress_js.go b/vendor/github.com/oov/psd/compress_js.go new file mode 100644 index 0000000000..0460d22b86 --- /dev/null +++ b/vendor/github.com/oov/psd/compress_js.go @@ -0,0 +1,40 @@ +// +build !wasm +// +build js + +package psd + +import ( + "errors" + "io" + + "github.com/gopherjs/gopherjs/js" +) + +func decodePackBits(dest []byte, r io.Reader, width int, lines int, large bool) (read int, err error) { + intSize := get4or8(large) >> 1 + b := make([]byte, lines*intSize) + var l int + if l, err = io.ReadFull(r, b); err != nil { + return + } + read += l + + lens := js.Global.Get("$github.com/oov/psd$").Call("decodePackBitsLines", b, lines, large).Interface().([]uint) + if lens == nil { + return read, errors.New("psd: error occurred in decodePackBitsLines") + } + + total := uint(0) + for _, ln := range lens { + total += ln + } + b = make([]byte, total) + if l, err = io.ReadFull(r, b); err != nil { + return + } + read += l + if !js.Global.Get("$github.com/oov/psd$").Call("decodePackBits", dest, b, lens).Bool() { + return read, errors.New("psd: error occurred in decodePackBits") + } + return +} diff --git a/vendor/github.com/oov/psd/compress_other.go b/vendor/github.com/oov/psd/compress_other.go new file mode 100644 index 0000000000..8f66ae29b3 --- /dev/null +++ b/vendor/github.com/oov/psd/compress_other.go @@ -0,0 +1,99 @@ +// +build !js + +package psd + +import ( + "io" + "runtime" + "sync" +) + +func decodePackBits(dest []byte, r io.Reader, width int, lines int, large bool) (read int, err error) { + buf := make([]byte, lines*(get4or8(large)>>1)) + var l int + if l, err = io.ReadFull(r, buf); err != nil { + return + } + read += l + + total := 0 + lens := make([]int, lines) + offsets := make([]int, lines) + ofs := 0 + if large { + for i := range lens { + l = int(readUint32(buf, ofs)) + lens[i] = l + offsets[i] = total + total += l + ofs += 4 + } + } else { + for i := range lens { + l = int(readUint16(buf, ofs)) + lens[i] = l + offsets[i] = total + total += l + ofs += 2 + } + } + + buf = make([]byte, total) + if l, err = io.ReadFull(r, buf); err != nil { + return + } + read += l + + n := runtime.GOMAXPROCS(0) + for n > 1 && n<<1 > lines { + n-- + } + if n == 1 { + decodePackBitsPerLine(dest, buf, lens) + return + } + + var wg sync.WaitGroup + wg.Add(n) + step := lines / n + ofs = 0 + for i := 1; i < n; i++ { + go func(dest []byte, buf []byte, lens []int) { + defer wg.Done() + decodePackBitsPerLine(dest, buf, lens) + }(dest[ofs*width:(ofs+step)*width], buf[offsets[ofs]:offsets[ofs+step]], lens[ofs:ofs+step]) + ofs += step + } + go func() { + defer wg.Done() + decodePackBitsPerLine(dest[ofs*width:], buf[offsets[ofs]:], lens[ofs:]) + }() + wg.Wait() + return +} + +func decodePackBitsPerLine(dest []byte, buf []byte, lens []int) { + var l int + for _, ln := range lens { + for i := 0; i < ln; { + if buf[i] <= 0x7f { + l = int(buf[i]) + 1 + copy(dest[:l], buf[i+1:]) + dest = dest[l:] + i += l + 1 + continue + } + if buf[i] == 0x80 { + i++ + continue + } + l = int(-buf[i]) + 1 + for j, c := 0, buf[i+1]; j < l; j++ { + dest[j] = c + } + dest = dest[l:] + i += 2 + } + buf = buf[ln:] + } +} diff --git a/vendor/github.com/oov/psd/compress_wasm_js.go b/vendor/github.com/oov/psd/compress_wasm_js.go new file mode 100644 index 0000000000..4f0c36c5f5 --- /dev/null +++ b/vendor/github.com/oov/psd/compress_wasm_js.go @@ -0,0 +1,99 @@ +// +build wasm,js + +package psd + +import ( + "io" + "runtime" + "sync" +) + +func decodePackBits(dest []byte, r io.Reader, width int, lines int, large bool) (read int, err error) { + buf := make([]byte, lines*(get4or8(large)>>1)) + var l int + if l, err = io.ReadFull(r, buf); err != nil { + return + } + read += l + + total := 0 + lens := make([]int, lines) + offsets := make([]int, lines) + ofs := 0 + if large { + for i := range lens { + l = int(readUint32(buf, ofs)) + lens[i] = l + offsets[i] = total + total += l + ofs += 4 + } + } else { + for i := range lens { + l = int(readUint16(buf, ofs)) + lens[i] = l + offsets[i] = total + total += l + ofs += 2 + } + } + + buf = make([]byte, total) + if l, err = io.ReadFull(r, buf); err != nil { + return + } + read += l + + n := runtime.GOMAXPROCS(0) + for n > 1 && n<<1 > lines { + n-- + } + if n == 1 { + decodePackBitsPerLine(dest, buf, lens) + return + } + + var wg sync.WaitGroup + wg.Add(n) + step := lines / n + ofs = 0 + for i := 1; i < n; i++ { + go func(dest []byte, buf []byte, lens []int) { + defer wg.Done() + decodePackBitsPerLine(dest, buf, lens) + }(dest[ofs*width:(ofs+step)*width], buf[offsets[ofs]:offsets[ofs+step]], lens[ofs:ofs+step]) + ofs += step + } + go func() { + defer wg.Done() + decodePackBitsPerLine(dest[ofs*width:], buf[offsets[ofs]:], lens[ofs:]) + }() + wg.Wait() + return +} + +func decodePackBitsPerLine(dest []byte, buf []byte, lens []int) { + var l int + for _, ln := range lens { + for i := 0; i < ln; { + if buf[i] <= 0x7f { + l = int(buf[i]) + 1 + copy(dest[:l], buf[i+1:]) + dest = dest[l:] + i += l + 1 + continue + } + if buf[i] == 0x80 { + i++ + continue + } + l = int(-buf[i]) + 1 + for j, c := 0, buf[i+1]; j < l; j++ { + dest[j] = c + } + dest = dest[l:] + i += 2 + } + buf = buf[ln:] + } +} diff --git a/vendor/github.com/oov/psd/imageres.go b/vendor/github.com/oov/psd/imageres.go new file mode 100644 index 0000000000..4e7b89ebfd --- /dev/null +++ b/vendor/github.com/oov/psd/imageres.go @@ -0,0 +1,108 @@ +package psd + +import ( + "errors" + "io" +) + +// ImageResource represents the image resource that is used in psd file. +type ImageResource struct { + Name string + Data []byte +} + +func readImageResource(r io.Reader) (resMap map[int]ImageResource, read int, err error) { + if Debug != nil { + Debug.Println("start - image resources section") + } + // Image Resources Section + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_69883 + var l int + b := make([]byte, 6) + if l, err = io.ReadFull(r, b[:4]); err != nil { + return nil, 0, err + } + read += l + imageResourceLen := int(readUint32(b, 0)) + if imageResourceLen == 0 { + return map[int]ImageResource{}, read, nil + } + + resMap = map[int]ImageResource{} + + // Image Resource Blocks + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_46269 + var id int + for read < imageResourceLen { + var res ImageResource + + // Signature: '8BIM' + if l, err = io.ReadFull(r, b[:6]); err != nil { + return nil, read, err + } + read += l + if string(b[:4]) != "8BIM" { + return nil, read, errors.New("psd: invalid image resource signature") + } + + // Unique identifier for the resource. + // Image resource IDs contains a list of resource IDs used by Photoshop. + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_38034 + id = int(readUint16(b, 4)) + + // Name: Pascal string, padded to make the size even + // (a null name consists of two bytes of 0) + if res.Name, l, err = readPascalString(r); err != nil { + return nil, read, err + } + read += l + if l, err = adjustAlign2(r, l); err != nil { + return nil, read, err + } + read += l + + // The resource data, described in the sections on the individual resource types. + // It is padded to make the size even. + if l, err = io.ReadFull(r, b[:4]); err != nil { + return nil, read, err + } + read += l + res.Data = make([]byte, readUint32(b, 0)) + if l, err = io.ReadFull(r, res.Data); err != nil { + return nil, read, err + } + read += l + if l, err = adjustAlign2(r, l); err != nil { + return nil, read, err + } + read += l + if Debug != nil { + Debug.Printf(" id: %04x(%d) name: %s dataLen: %d", id, id, res.Name, len(res.Data)) + } + resMap[id] = res + } + if Debug != nil { + Debug.Println("end - image resources section") + } + return resMap, read, nil +} + +func hasAlphaID0(Res map[int]ImageResource) bool { + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_38034 + // 0x041D(1053) | (Photoshop 6.0) Alpha Identifiers. + // 4 bytes of length, followed by 4 bytes each for every alpha identifier. + // + // This description is probably a little different from the actual structure. + // "4 bytes of length" is included in Image Resource Blocks structure, not in the resource data block. + // so alpha identifier appears from the head in the resource data block. + aid, ok := Res[0x041D] + if !ok { + return true + } + for i, ln := 0, len(aid.Data); i < ln; i += 4 { + if readUint32(aid.Data, i) == 0 { + return true + } + } + return false +} diff --git a/vendor/github.com/oov/psd/layer.go b/vendor/github.com/oov/psd/layer.go new file mode 100644 index 0000000000..7750350970 --- /dev/null +++ b/vendor/github.com/oov/psd/layer.go @@ -0,0 +1,875 @@ +package psd + +import ( + "errors" + "image" + "io" +) + +// Layer represents layer. +type Layer struct { + SeqID int + + Name string + UnicodeName string + MBCSName string + + Rect image.Rectangle + // Channel key is the channel ID. + // 0 = red, 1 = green, etc. + // -1 = transparency mask + // -2 = user supplied layer mask + // -3 = real user supplied layer mask + // (when both a user mask and a vector mask are present) + Channel map[int]Channel + BlendMode BlendMode + Opacity uint8 + Clipping bool + BlendClippedElements bool + Flags uint8 + Mask Mask + Picker image.Image + AdditionalLayerInfo map[AdditionalInfoKey][]byte + SectionDividerSetting struct { + Type int + BlendMode BlendMode + SubType int + } + + Layer []Layer +} + +// TransparencyProtected returns whether the layer's transparency being protected. +func (l *Layer) TransparencyProtected() bool { + return l.Flags&1 != 0 +} + +// Visible returns whether the layer is visible. +func (l *Layer) Visible() bool { + return l.Flags&2 == 0 +} + +// HasImage returns whether the layer has an image data. +func (l *Layer) HasImage() bool { + return l.SectionDividerSetting.Type == 0 +} + +// Folder returns whether the layer is folder(group layer). +func (l *Layer) Folder() bool { + return l.SectionDividerSetting.Type == 1 || l.SectionDividerSetting.Type == 2 +} + +// FolderIsOpen returns whether the folder is opened when layer is folder. +func (l *Layer) FolderIsOpen() bool { + return l.SectionDividerSetting.Type == 1 +} + +func (l *Layer) String() string { + return l.Name +} + +// Mask represents layer mask and vector mask. +type Mask struct { + Rect image.Rectangle + DefaultColor int + Flags int + + RealRect image.Rectangle + RealBackgroundColor int + RealFlags int + + UserMaskDensity int + UserMaskFeather float64 + VectorMaskDensity int + VectorMaskFeather float64 +} + +// Enabled returns whether the mask is enabled. +func (m *Mask) Enabled() bool { + return m.Flags&2 == 0 +} + +// RealEnabled returns whether the user real mask is enabled. +func (m *Mask) RealEnabled() bool { + return m.RealFlags&2 == 0 +} + +// Channel represents a channel of the color. +type Channel struct { + // Data is uncompressed channel image data. + Data []byte + Picker image.Image +} + +func readLayerAndMaskInfo(r io.Reader, cfg *Config, o *DecodeOptions) (psd *PSD, read int, err error) { + if Debug != nil { + Debug.Println("start - layer and mask information section") + } + + psd = &PSD{ + Config: *cfg, + } + + // Layer and Mask Information Section + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_75067 + intSize := get4or8(cfg.PSB()) + b := make([]byte, intSize) + var l int + if l, err = io.ReadFull(r, b[:intSize]); err != nil { + return nil, 0, err + } + read += l + layerAndMaskInfoLen := int(readUint(b, 0, intSize)) + if Debug != nil { + Debug.Println(" layerAndMaskInfoLen:", layerAndMaskInfoLen) + reportReaderPosition(" file offset: 0x%08x", r) + } + if layerAndMaskInfoLen == 0 { + return psd, read, nil + } + + var layer []Layer + if read < layerAndMaskInfoLen+intSize { + if layer, l, err = readLayerInfo(r, cfg, o); err != nil { + return nil, read, err + } + read += l + } + + if layerAndMaskInfoLen+intSize-read >= 4 { + // Global layer mask info + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115 + if l, err = io.ReadFull(r, b[:4]); err != nil { + return nil, read, err + } + read += l + if globalLayerMaskInfoLen := int(readUint32(b, 0)); globalLayerMaskInfoLen > 0 { + if Debug != nil { + Debug.Println(" globalLayerMaskInfoLen:", globalLayerMaskInfoLen) + reportReaderPosition(" file offset: 0x%08x", r) + } + // TODO(oov): implement + if l, err = io.ReadFull(r, make([]byte, globalLayerMaskInfoLen)); err != nil { + return nil, read, err + } + read += l + } + } + + if layerAndMaskInfoLen+intSize-read >= 8 { + var layer2 []Layer + if psd.AdditinalLayerInfo, layer2, l, err = readAdditionalLayerInfo(r, layerAndMaskInfoLen+intSize-read, cfg, o); err != nil { + return nil, read, err + } + read += l + if layer != nil && layer2 != nil { + return nil, read, errors.New("psd: unexpected layer structure") + } + if layer2 != nil { + layer = layer2 + } + } + + if psd.Layer, err = buildTree(layer); err != nil { + return nil, read, err + } + + if remain := layerAndMaskInfoLen + intSize - read; remain != 0 && remain < intSize { + if l, err = discard(r, remain); err != nil { + return nil, read, err + } + read += l + } + + if read != layerAndMaskInfoLen+intSize { + return nil, read, errors.New("psd: layer and mask info read size mismatched. expected " + itoa(layerAndMaskInfoLen+4) + " actual " + itoa(read)) + } + if Debug != nil { + Debug.Println("end - layer and mask information section") + } + return psd, read, nil +} + +func buildTree(layer []Layer) ([]Layer, error) { + // idx type + // 0: 3 + // 1: 3 + // 2: 0 + // 3: 0 + // 4: 1 + // 5: 0 + // 6: 0 + // 7: 1 + stack := make([][]Layer, 0, 8) + n := []Layer{} + for i, l := range layer { + switch l.SectionDividerSetting.Type { + case 1, 2: + l.Layer = n + if len(stack) == 0 { + return nil, errors.New("psd: layer tree structure is broken(#" + itoa(i) + ")") + } + n = stack[len(stack)-1] + stack = stack[:len(stack)-1] + n = append(n, l) + case 3: + stack = append(stack, n) + n = []Layer{} + default: + n = append(n, l) + } + } + return n, nil +} + +func readSectionDividerSetting(l *Layer) (typ int, blendMode BlendMode, subType int, err error) { + b, ok := l.AdditionalLayerInfo[AdditionalInfoKeySectionDividerSetting] + if ok { + typ = int(readUint32(b, 0)) + if len(b) < 12 { + return typ, "", 0, nil + } + if string(b[4:8]) != sectionSignature { + return 0, "", 0, errors.New("psd: unexpected signature in section divider setting") + } + blendMode = BlendMode(b[8:12]) + if len(b) < 16 { + return typ, blendMode, 0, nil + } + subType = int(readUint32(b, 12)) + return typ, blendMode, subType, nil + } + b, ok = l.AdditionalLayerInfo[AdditionalInfoKeySectionDividerSetting2] + if ok { + typ = int(readUint32(b, 0)) + if len(b) < 12 { + return typ, "", 0, nil + } + if string(b[4:8]) != sectionSignature { + return 0, "", 0, errors.New("psd: unexpected signature in section divider setting 2") + } + blendMode = BlendMode(b[8:12]) + if len(b) < 16 { + return typ, blendMode, 0, nil + } + subType = int(readUint32(b, 12)) + return typ, blendMode, subType, nil + } + return 0, "", 0, nil +} + +type channelData struct { + ChIndex int + DataLen int +} + +type layerChannelInfo struct { + Layer *Layer + ChannelData []channelData +} + +func readLayerInfo(r io.Reader, cfg *Config, o *DecodeOptions) (layer []Layer, read int, err error) { + intSize := get4or8(cfg.PSB()) + if Debug != nil { + Debug.Println("start - layer info section") + } + b := make([]byte, 16) + var l int + if l, err = io.ReadFull(r, b[:intSize]); err != nil { + return nil, 0, err + } + read += l + layerInfoLen := int(readUint(b, 0, intSize)) + if Debug != nil { + Debug.Println(" layerInfoLen:", layerInfoLen) + } + if layerInfoLen == 0 { + if Debug != nil { + Debug.Println("end - layer info section") + } + return nil, read, nil + } + + if l, err = io.ReadFull(r, b[:2]); err != nil { + return nil, read, err + } + read += l + numLayers := readUint16(b, 0) + if numLayers&0x8000 != 0 { + numLayers = ^numLayers + 1 + // how can I use this info...? + // hasAlphaForMergedResult = true + } + if Debug != nil { + Debug.Println(" numLayers:", numLayers) + } + + layerSlice := make([]Layer, numLayers) + lcis := make([]layerChannelInfo, numLayers) + for i := range layerSlice { + if Debug != nil { + Debug.Printf("start - layer #%d structure", i) + } + layer := &layerSlice[i] + layer.SeqID = i + + if l, err = io.ReadFull(r, b[:16]); err != nil { + return nil, read, err + } + read += l + layer.Rect = image.Rect( + int(int32(readUint32(b, 4))), int(int32(readUint32(b, 0))), + int(int32(readUint32(b, 12))), int(int32(readUint32(b, 8))), + ) + + if l, err = io.ReadFull(r, b[:2]); err != nil { + return nil, read, err + } + read += l + numChannels := int(readUint16(b, 0)) + + lci := layerChannelInfo{ + Layer: layer, + ChannelData: make([]channelData, numChannels), + } + for j := 0; j < numChannels; j++ { + if l, err = io.ReadFull(r, b[:2+intSize]); err != nil { + return nil, read, err + } + read += l + lci.ChannelData[j] = channelData{ + ChIndex: int(int16(readUint16(b, 0))), + DataLen: int(readUint(b, 2, intSize)), + } + } + lcis[i] = lci + + if l, err = io.ReadFull(r, b[:12]); err != nil { + return nil, read, err + } + read += l + if string(b[:4]) != sectionSignature { + return nil, read, errors.New("psd: unexpected the blend mode signature") + } + layer.BlendMode = BlendMode(b[4:8]) + layer.Opacity = b[8] + layer.Clipping = b[9] != 0 + layer.Flags = b[10] + // b[11] - Filler(zero) + if l, err = readLayerExtraData(r, layer, cfg, o); err != nil { + return nil, read, err + } + read += l + + if data, ok := layer.AdditionalLayerInfo[AdditionalInfoKeyUnicodeLayerName]; ok && data != nil { + layer.UnicodeName = readUnicodeString(data) + layer.Name = layer.UnicodeName + delete(layer.AdditionalLayerInfo, AdditionalInfoKeyUnicodeLayerName) + } else { + layer.Name = layer.MBCSName + } + + // this is called "Blend Clipped Layers as Group" in Photoshop's Layer Style dialog. + if data, ok := layer.AdditionalLayerInfo[AdditionalInfoKeyBlendClippingElements]; ok && data != nil { + layer.BlendClippedElements = data[0] != 0 + delete(layer.AdditionalLayerInfo, AdditionalInfoKeyBlendClippingElements) + } else { + layer.BlendClippedElements = true + } + + if layer.SectionDividerSetting.Type, + layer.SectionDividerSetting.BlendMode, + layer.SectionDividerSetting.SubType, + err = readSectionDividerSetting(layer); err != nil { + return nil, read, err + } + if layer.SectionDividerSetting.BlendMode == "" { + layer.SectionDividerSetting.BlendMode = layer.BlendMode + } + delete(layer.AdditionalLayerInfo, AdditionalInfoKeySectionDividerSetting) + + if Debug != nil { + Debug.Printf("layer #%d", layer.SeqID) + Debug.Println(" Name:", layer.Name) + Debug.Println(" Rect:", layer.Rect) + Debug.Println(" Channels:", len(layer.Channel)) + Debug.Println(" BlendMode:", layer.BlendMode) + Debug.Println(" Opacity:", layer.Opacity) + Debug.Println(" Clipping:", layer.Clipping) + Debug.Println(" Flags:", layer.Flags) + Debug.Println(" TransparecyProtected:", layer.TransparencyProtected()) + Debug.Println(" Visible:", layer.Visible()) + Debug.Println(" Photoshop 5.0 and later:", layer.Flags&8 != 0) + // this information is useful for the group layer, + // but sadly it isn't written correctly by some other softwares. + Debug.Println(" Pixel data irrelevant to appearance of document:", layer.Flags&8 != 0 && layer.Flags&16 != 0) + Debug.Println(" SectionDividerSetting:") + Debug.Println(" Type:", layer.SectionDividerSetting.Type) + Debug.Println(" BlendMode:", layer.SectionDividerSetting.BlendMode) + Debug.Println(" SubType:", layer.SectionDividerSetting.SubType) + Debug.Println(" LayerMask:") + Debug.Println(" Rect:", layer.Mask.Rect) + Debug.Println(" DefaultColor:", layer.Mask.DefaultColor) + Debug.Println(" Flags:", layer.Mask.Flags) + Debug.Println(" Position relative to layer:", layer.Mask.Flags&1 != 0) + Debug.Println(" Layer mask enabled:", layer.Mask.Enabled()) + Debug.Println(" Invert layer mask when blending (Obsolete):", layer.Mask.Flags&4 != 0) + Debug.Println(" The user mask actually came from rendering other data:", layer.Mask.Flags&8 != 0) + Debug.Printf("end - layer #%d structure", i) + } + } + if !o.SkipLayerImage { + if l, err = readLayerImage(lcis, r, cfg, o); err != nil { + return nil, read, err + } + read += l + if l, err = adjustAlign4(r, read+4-layerInfoLen); err != nil { + return nil, read, err + } + read += l + } else { + if l, err = discard(r, layerInfoLen+4-read); err != nil { + return nil, read, err + } + read += l + } + + if layerInfoLen+intSize != read { + return nil, read, errors.New("psd: layer info read size mismatched. expected " + itoa(layerInfoLen+4) + " actual " + itoa(read)) + } + if Debug != nil { + Debug.Println("end - layer info section") + } + return layerSlice, read, nil +} + +func readLayerImage(lcis []layerChannelInfo, r io.Reader, cfg *Config, o *DecodeOptions) (read int, err error) { + var l int + b := make([]byte, 2) + for i, lci := range lcis { + chMap := map[int]Channel{} + pickerChs := make([][]byte, cfg.ColorMode.Channels(), 8) + for _, j := range lci.ChannelData { + var readCh int + if j.DataLen >= 2 { + if l, err = io.ReadFull(r, b[:2]); err != nil { + return read, err + } + readCh += l + read += l + } + cmpMethod := CompressionMethod(readUint16(b, 0)) + if Debug != nil { + Debug.Printf(" layer #%d %q channel #%d image data", i, lci.Layer.Name, j.ChIndex) + Debug.Println(" length:", j.DataLen, "compression method:", cmpMethod) + reportReaderPosition(" file offset: 0x%08x", r) + } + + var rect image.Rectangle + switch j.ChIndex { + case -3: + rect = lci.Layer.Mask.RealRect + case -2: + rect = lci.Layer.Mask.Rect + default: + rect = lci.Layer.Rect + } + data := make([]byte, (rect.Dx()*cfg.Depth+7)>>3*rect.Dy()) + pk := findGrayPicker(cfg.Depth) + pk.setSource(rect, data) + + if j.DataLen > 2 { + if l, err = cmpMethod.Decode(data, r, int64(j.DataLen-2), rect, cfg.Depth, 1, cfg.PSB()); err != nil { + return read, err + } + readCh += l + read += l + } + // It seems we can discard in this case + if readCh < j.DataLen { + if l, err = discard(r, j.DataLen-readCh); err != nil { + return read, err + } + readCh += l + read += l + } + if readCh != j.DataLen { + return read, errors.New("psd: layer: " + itoa(i) + " channel: " + itoa(j.ChIndex) + " read size mismatched. expected " + itoa(j.DataLen) + " actual " + itoa(readCh)) + } + + chMap[j.ChIndex] = Channel{ + Data: data, + Picker: pk, + } + switch { + case j.ChIndex >= 0: + pickerChs[j.ChIndex] = data + case j.ChIndex == -1: + pickerChs = append(pickerChs, data) + } + } + pk := findPicker(cfg.Depth, cfg.ColorMode, cfg.ColorMode.Channels() < len(pickerChs)) + pk.setSource(lci.Layer.Rect, pickerChs...) + lci.Layer.Picker = pk + lci.Layer.Channel = chMap + if o.LayerImageLoaded != nil { + o.LayerImageLoaded(lci.Layer, i, len(lcis)) + } + } + return read, nil +} + +func readLayerExtraData(r io.Reader, layer *Layer, cfg *Config, o *DecodeOptions) (read int, err error) { + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_22582 + // Layer mask / adjustment layer data + if Debug != nil { + Debug.Println("start - layer extra data section") + } + b := make([]byte, 16) + var l int + if l, err = io.ReadFull(r, b[:4]); err != nil { + return 0, err + } + read += l + extraDataLen := int(readUint32(b, 0)) + if Debug != nil { + Debug.Println(" extraDataLen:", extraDataLen) + } + if extraDataLen == 0 { + return read, err + } + + l, err = readLayerMaskAndAdjustmentLayerData(r, layer, cfg, o) + read += l + if err != nil { + return read, err + } + + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_21332 + // Layer blending ranges data + if l, err = io.ReadFull(r, b[:4]); err != nil { + return read, err + } + read += l + if blendingRangesLen := int(readUint32(b, 0)); blendingRangesLen > 0 { + if Debug != nil { + Debug.Println(" layer blending ranges data skipped:", blendingRangesLen) + } + // TODO(oov): implement + if l, err = io.ReadFull(r, make([]byte, blendingRangesLen)); err != nil { + return read, err + } + read += l + } + + // Layer name: Pascal string, padded to a multiple of 4 bytes. + if layer.MBCSName, l, err = readPascalString(r); err != nil { + return read, err + } + read += l + if l, err = adjustAlign4(r, l); err != nil { + return read, err + } + read += l + + if read < extraDataLen+4 { + var layers []Layer + if layer.AdditionalLayerInfo, layers, l, err = readAdditionalLayerInfo(r, extraDataLen+4-read, cfg, o); err != nil { + return read, err + } + read += l + if len(layers) > 0 { + return read, errors.New("psd: unexpected layer structure") + } + } else { + layer.AdditionalLayerInfo = map[AdditionalInfoKey][]byte{} + } + + if extraDataLen+4 != read { + return read, errors.New("psd: layer extra info read size mismatched. expected " + itoa(extraDataLen+4) + " actual " + itoa(read)) + } + if Debug != nil { + Debug.Println("end - layer extra data section") + } + return read, nil +} + +func readLayerMaskAndAdjustmentLayerData(r io.Reader, layer *Layer, cfg *Config, o *DecodeOptions) (read int, err error) { + // Layer mask / adjustment layer data + // Can be 40 bytes, 24 bytes, or 4 bytes if no layer mask. + // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_22582 + var l int + b := make([]byte, 16) + if l, err = io.ReadFull(r, b[:4]); err != nil { + return read, err + } + read += l + + maskLen := int(readUint32(b, 0)) + if maskLen == 0 { + // no layer mask + return read, nil + } + var readMask int + + if maskLen-readMask < 16 { + // "Rectangle enclosing layer mask" is not present. + if maskLen-readMask > 0 { + l, err = discard(r, maskLen-readMask) + read += l + if err != nil { + return read, err + } + } + return read, nil + } + if l, err = io.ReadFull(r, b[:16]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.Rect = image.Rect( + int(int32(readUint32(b, 4))), int(int32(readUint32(b, 0))), + int(int32(readUint32(b, 12))), int(int32(readUint32(b, 8))), + ) + + if maskLen-readMask < 1 { + // "Default color" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.DefaultColor = int(b[0]) + + // bit 0 = position relative to layer + // bit 1 = layer mask disabled + // bit 2 = invert layer mask when blending (Obsolete) + // bit 3 = indicates that the user mask actually came from rendering other data + // bit 4 = indicates that the user and/or vector masks have parameters applied to them + if maskLen-readMask < 1 { + // "Flags" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.Flags = int(b[0]) + + layer.Mask.UserMaskDensity = 255 + layer.Mask.VectorMaskDensity = 255 + + // Mask Parameters. Only present if bit 4 of Flags set above. + if layer.Mask.Flags&16 != 0 { + if maskLen-readMask < 1 { + // "Mask Parameters" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + + // Mask Parameters bit flags present as follows: + // bit 0 = user mask density, 1 byte + // bit 1 = user mask feather, 8 byte, double + // bit 2 = vector mask density, 1 byte + // bit 3 = vector mask feather, 8 bytes, double + maskParam := int(b[0]) + if maskParam&1 != 0 { + if maskLen-readMask < 1 { + // "user mask density" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.UserMaskDensity = int(b[0]) + } + if maskParam&2 != 0 { + if maskLen-readMask < 8 { + // "user mask feather" is not present. + if maskLen-readMask > 0 { + l, err = discard(r, maskLen-readMask) + read += l + if err != nil { + return read, err + } + } + return read, nil + } + if l, err = io.ReadFull(r, b[:8]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.UserMaskFeather = readFloat64(b, 0) + } + if maskParam&4 != 0 { + if maskLen-readMask < 1 { + // "vector mask density" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.VectorMaskDensity = int(b[0]) + } + if maskParam&8 != 0 { + if maskLen-readMask < 8 { + // "vector mask feather" is not present. + if maskLen-readMask > 0 { + l, err = discard(r, maskLen-readMask) + read += l + if err != nil { + return read, err + } + } + return read, nil + } + if l, err = io.ReadFull(r, b[:8]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.VectorMaskFeather = readFloat64(b, 0) + } + } + + if maskLen == 20 { + // Padding. Only present if size = 20. + if l, err = io.ReadFull(r, b[:2]); err != nil { + return read, err + } + read += l + readMask += l + return read, nil + } + + if maskLen-readMask < 1 { + // "Real Flags" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.RealFlags = int(b[0]) + + if maskLen-readMask < 1 { + // "Real user mask background" is not present. + return read, nil + } + if l, err = io.ReadFull(r, b[:1]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.RealBackgroundColor = int(b[0]) + + if maskLen-readMask < 16 { + // "Rectangle enclosing layer mask" is not present. + if maskLen-readMask > 0 { + l, err = discard(r, maskLen-readMask) + read += l + if err != nil { + return read, err + } + } + return read, nil + } + if l, err = io.ReadFull(r, b[:16]); err != nil { + return read, err + } + read += l + readMask += l + layer.Mask.RealRect = image.Rect( + int(readUint32(b, 4)), int(readUint32(b, 0)), + int(readUint32(b, 12)), int(readUint32(b, 8)), + ) + + return read, nil +} + +func readAdditionalLayerInfo(r io.Reader, infoLen int, cfg *Config, o *DecodeOptions) (infos map[AdditionalInfoKey][]byte, layers []Layer, read int, err error) { + if Debug != nil { + Debug.Println("start - additional layer info section") + Debug.Println(" infoLen:", infoLen) + } + infos = map[AdditionalInfoKey][]byte{} + b := make([]byte, 8) + var l int + for read < infoLen { + // Sometimes I encountered padded section and unpadded section, + // so find the signature to avoid failure. + b[0] = 0 + for read < infoLen && b[0] != '8' { + if l, err = io.ReadFull(r, b[:1]); err != nil { + return nil, nil, read, err + } + read += l + } + if b[0] != '8' { + break + } + if l, err = io.ReadFull(r, b[1:]); err != nil { + return nil, nil, read, err + } + read += l + + if sig := string(b[:4]); sig != sectionSignature && sig != "8B64" { + return nil, nil, read, errors.New("psd: unexpected the signature found in the additional layer information block") + } + + key := AdditionalInfoKey(b[4:]) + switch key { + case AdditionalInfoKeyLayerInfo, + AdditionalInfoKeyLayerInfo16, + AdditionalInfoKeyLayerInfo32: + if Debug != nil { + Debug.Println(" key:", key) + } + var layrs []Layer + if layrs, l, err = readLayerInfo(r, cfg, o); err != nil { + return nil, nil, read, err + } + read += l + layers = append(layers, layrs...) + default: + intSize := key.LenSize(cfg.PSB()) + if l, err = io.ReadFull(r, b[:intSize]); err != nil { + return nil, nil, read, err + } + read += l + var data []byte + if ln := int(readUint(b, 0, intSize)); ln > 0 { + data = make([]byte, ln) + if l, err = io.ReadFull(r, data); err != nil { + return nil, nil, read, err + } + read += l + } + infos[key] = data + if Debug != nil { + Debug.Println(" key:", key, "dataLen:", len(data)) + } + } + } + if infoLen != read { + return nil, nil, read, errors.New("psd: additional layer info read size mismatched. expected " + itoa(infoLen) + " actual " + itoa(read)) + } + if Debug != nil { + Debug.Println("end - additional layer info section") + } + return infos, layers, read, nil +} diff --git a/vendor/github.com/oov/psd/picker.go b/vendor/github.com/oov/psd/picker.go new file mode 100644 index 0000000000..ffdf33095b --- /dev/null +++ b/vendor/github.com/oov/psd/picker.go @@ -0,0 +1,456 @@ +package psd + +import ( + "image" + "image/color" + + psdColor "github.com/oov/psd/color" +) + +type picker interface { + image.Image + setSource(rect image.Rectangle, src ...[]byte) +} + +func findPicker(depth int, colorMode ColorMode, hasAlpha bool) picker { + switch colorMode { + case ColorModeBitmap, ColorModeGrayscale: + return findNGrayPicker(depth, hasAlpha) + case ColorModeRGB: + return findNRGBAPicker(depth, hasAlpha) + case ColorModeCMYK: + return findNCMYKAPicker(depth, hasAlpha) + } + return nil +} + +func findGrayPicker(depth int) picker { + switch depth { + case 1: + return &pickerGray1{} + case 8: + return &pickerGray8{} + case 16: + return &pickerGray16{} + case 32: + return &pickerGray32{} + } + return nil +} + +func findNGrayPicker(depth int, hasAlpha bool) picker { + switch depth { + case 8: + if hasAlpha { + return &pickerNGrayA8{} + } + return &pickerNGray8{} + case 16: + if hasAlpha { + return &pickerNGrayA16{} + } + return &pickerNGray16{} + case 32: + if hasAlpha { + return &pickerNGrayA32{} + } + return &pickerNGray32{} + } + return nil +} + +func findNRGBAPicker(depth int, hasAlpha bool) picker { + switch depth { + case 8: + if hasAlpha { + return &pickerNRGBA8{} + } + return &pickerNRGB8{} + case 16: + if hasAlpha { + return &pickerNRGBA16{} + } + return &pickerNRGB16{} + case 32: + if hasAlpha { + return &pickerNRGBA32{} + } + return &pickerNRGB32{} + } + return nil +} + +func findNCMYKAPicker(depth int, hasAlpha bool) picker { + switch depth { + case 8: + if hasAlpha { + return &pickerNCMYKA8{} + } + return &pickerNCMYK8{} + case 16: + if hasAlpha { + return &pickerNCMYKA16{} + } + return &pickerNCMYK16{} + } + return nil +} + +type pickerPalette struct { + Rect image.Rectangle + Src []byte + Palette color.Palette +} + +func (p *pickerPalette) setSource(rect image.Rectangle, src ...[]byte) { p.Rect, p.Src = rect, src[0] } +func (p *pickerPalette) ColorModel() color.Model { return p.Palette } +func (p *pickerPalette) Bounds() image.Rectangle { return p.Rect } +func (p *pickerPalette) At(x, y int) color.Color { + pos := (y-p.Rect.Min.Y)*p.Rect.Dx() + x - p.Rect.Min.X + return p.Palette[p.Src[pos]] +} + +type pickerGray1 struct { + Rect image.Rectangle + Y []byte +} + +func (p *pickerGray1) setSource(rect image.Rectangle, src ...[]byte) { p.Rect, p.Y = rect, src[0] } +func (p *pickerGray1) ColorModel() color.Model { return psdColor.Gray1Model } +func (p *pickerGray1) Bounds() image.Rectangle { return p.Rect } +func (p *pickerGray1) At(x, y int) color.Color { + xx := x - p.Rect.Min.X + pos := (p.Rect.Dx()+7)>>3*(y-p.Rect.Min.Y) + xx>>3 + return psdColor.Gray1{Y: p.Y[pos]&(1< cfg.ColorMode.Channels() && hasAlphaID0(cfg.Res)) + } + if pk == nil { + return nil, errors.New("psd: color mode " + itoa(int(cfg.ColorMode)) + " depth " + itoa(cfg.Depth) + " is not implemeted") + } + return pk, nil +} + +// PSD represents Photoshop image file. +type PSD struct { + Config Config + Channel map[int]Channel + Layer []Layer + AdditinalLayerInfo map[AdditionalInfoKey][]byte + // Data is uncompressed merged image data. + Data []byte + Picker image.Image +} + +// DecodeConfig returns the color model and dimensions of a image without decoding the entire image. +func DecodeConfig(r io.Reader) (cfg Config, read int, err error) { + const ( + fileHeaderLen = 4 + 2 + 6 + 2 + 4 + 4 + 2 + 2 + colorModeDataLen = 4 + ) + b := make([]byte, fileHeaderLen+colorModeDataLen) + var l int + if l, err = io.ReadFull(r, b); err != nil { + return Config{}, 0, err + } + read += l + + if string(b[:4]) != headerSignature { // Signature + return Config{}, read, errors.New("psd: invalid format") + } + cfg.Version = int(readUint16(b, 4)) + if cfg.Version != 1 && cfg.Version != 2 { // Version + return Config{}, read, errors.New("psd: unexpected file version: " + itoa(cfg.Version)) + } + + // b[6:12] Reserved + + cfg.Channels = int(readUint16(b, 12)) + if cfg.Channels < 1 || cfg.Channels > 56 { + return Config{}, read, errors.New("psd: unexpected the number of channels") + } + + width, height := int(readUint32(b, 18)), int(readUint32(b, 14)) + if height < 1 || (cfg.Version == 1 && height > 30000) || (cfg.Version == 2 && height > 300000) { + return Config{}, read, errors.New("psd: unexpected the height of the image") + } + if width < 1 || (cfg.Version == 1 && width > 30000) || (cfg.Version == 2 && width > 300000) { + return Config{}, read, errors.New("psd: unexpected the width of the image") + } + cfg.Rect = image.Rect(0, 0, width, height) + + cfg.Depth = int(readUint16(b, 22)) + if cfg.Depth != 1 && cfg.Depth != 8 && cfg.Depth != 16 && cfg.Depth != 32 { + return Config{}, read, errors.New("psd: unexpected color depth") + } + + cfg.ColorMode = ColorMode(readUint16(b, 24)) + + if ln := readUint32(b, 26); ln > 0 { + cfg.ColorModeData = make([]byte, ln) + if l, err = io.ReadFull(r, cfg.ColorModeData); err != nil { + return Config{}, read, err + } + read += l + } + if Debug != nil { + Debug.Println("start - header") + Debug.Printf(" width: %d height: %d", width, height) + Debug.Printf(" channels: %d depth: %d colorMode %d", cfg.Channels, cfg.Depth, cfg.ColorMode) + Debug.Printf(" colorModeDataLen: %d", len(cfg.ColorModeData)) + Debug.Println("end - header") + } + + if cfg.Res, l, err = readImageResource(r); err != nil { + return Config{}, read, err + } + read += l + return cfg, read, nil +} + +// DecodeOptions are the decoding options. +type DecodeOptions struct { + SkipLayerImage bool + SkipMergedImage bool + ConfigLoaded func(cfg Config) error + LayerImageLoaded func(layer *Layer, index int, total int) +} + +// Decode reads a image from r and returns it. +// Default parameters are used if a nil *DecodeOptions is passed. +func Decode(r io.Reader, o *DecodeOptions) (psd *PSD, read int, err error) { + if o == nil { + o = &DecodeOptions{} + } + cfg, l, err := DecodeConfig(r) + if err != nil { + return nil, 0, err + } + read += l + if o.ConfigLoaded != nil { + err = o.ConfigLoaded(cfg) + if err != nil { + return nil, 0, err + } + } + + psd, l, err = readLayerAndMaskInfo(r, &cfg, o) + if err != nil { + return nil, read, err + } + read += l + + if !o.SkipMergedImage { + // allocate image buffer. + // `+7` and `>>3` is the padding for the 1bit depth color mode. + plane := (psd.Config.Rect.Dx()*psd.Config.Depth + 7) >> 3 * psd.Config.Rect.Dy() + psd.Data = make([]byte, plane*psd.Config.Channels) + chs := make([][]byte, psd.Config.Channels) + psd.Channel = make(map[int]Channel) + for i := 0; i < psd.Config.Channels; i++ { + data := psd.Data[plane*i : plane*(i+1) : plane*(i+1)] + chs[i] = data + pk := findGrayPicker(psd.Config.Depth) + pk.setSource(psd.Config.Rect, data) + psd.Channel[i] = Channel{ + Data: data, + Picker: pk, + } + } + var pk picker + if pk, err = cfg.makePicker(); err != nil { + return nil, read, err + } + pk.setSource(psd.Config.Rect, chs...) + psd.Picker = pk + + b := make([]byte, 2) + if l, err = io.ReadFull(r, b[:2]); err != nil { + return nil, read, err + } + read += l + cmpMethod := CompressionMethod(readUint16(b, 0)) + l, err = cmpMethod.Decode( + psd.Data, + r, + 0, + psd.Config.Rect, + psd.Config.Depth, + psd.Config.Channels, + psd.Config.PSB(), + ) + if err != nil { + return nil, read, err + } + read += l + } + return psd, read, nil +} + +func decode(r io.Reader) (image.Image, error) { + psd, _, err := Decode(r, &DecodeOptions{SkipLayerImage: true}) + if err != nil { + return nil, err + } + return psd.Picker, nil +} + +func decodeConfig(r io.Reader) (image.Config, error) { + cfg, _, err := DecodeConfig(r) + if err != nil { + return image.Config{}, err + } + var pk picker + if pk, err = cfg.makePicker(); err != nil { + return image.Config{}, err + } + return image.Config{ + Width: cfg.Rect.Dx(), + Height: cfg.Rect.Dy(), + ColorModel: pk.ColorModel(), + }, nil +} + +func init() { + image.RegisterFormat("psd", headerSignature+"\x00\x01", decode, decodeConfig) + image.RegisterFormat("psb", headerSignature+"\x00\x02", decode, decodeConfig) +} diff --git a/vendor/github.com/oov/psd/util.go b/vendor/github.com/oov/psd/util.go new file mode 100644 index 0000000000..47d2279f57 --- /dev/null +++ b/vendor/github.com/oov/psd/util.go @@ -0,0 +1,156 @@ +package psd + +import ( + "io" + "math" + "unicode/utf16" +) + +func itoa(x int) string { + var b [32]byte + var minus bool + if x < 0 { + minus = true + x = -x + } + i := len(b) - 1 + for x > 9 { + b[i] = byte(x%10 + '0') + x /= 10 + i-- + } + b[i] = byte(x + '0') + if minus { + i-- + b[i] = '-' + } + return string(b[i:]) +} + +func readUint16(b []byte, offset int) uint16 { + return uint16(b[offset])<<8 | uint16(b[offset+1]) +} + +func writeUint16(b []byte, v uint16, offset int) { + b[offset] = uint8(v >> 8) + b[offset+1] = uint8(v) +} + +func readUint32(b []byte, offset int) uint32 { + return uint32(b[offset])<<24 | uint32(b[offset+1])<<16 | uint32(b[offset+2])<<8 | uint32(b[offset+3]) +} + +func readUint64(b []byte, offset int) uint64 { + return uint64(b[offset])<<56 | uint64(b[offset+1])<<48 | uint64(b[offset+2])<<40 | uint64(b[offset+3])<<32 | + uint64(b[offset+4])<<24 | uint64(b[offset+5])<<16 | uint64(b[offset+6])<<8 | uint64(b[offset+7]) +} + +func get4or8(is64 bool) int { + if is64 { + return 8 + } + return 4 +} + +func readUint(b []byte, offset int, size int) uint64 { + switch size { + case 8: + return readUint64(b, offset) + case 4: + return uint64(readUint32(b, offset)) + case 2: + return uint64(readUint16(b, offset)) + case 1: + return uint64(b[offset]) + } + panic("psd: unexpected size") +} + +func readFloat32(b []byte, offset int) float32 { + return math.Float32frombits(readUint32(b, offset)) +} + +func readFloat64(b []byte, offset int) float64 { + return math.Float64frombits(readUint64(b, offset)) +} + +func writeUint32(b []byte, v uint32, offset int) { + b[offset] = uint8(v >> 24) + b[offset+1] = uint8(v >> 16) + b[offset+2] = uint8(v >> 8) + b[offset+3] = uint8(v) +} + +func readUnicodeString(b []byte) string { + ln := readUint32(b, 0) + if ln == 0 { + return "" + } + buf := make([]uint16, ln) + for i := range buf { + buf[i] = readUint16(b, 4+i<<1) + } + return string(utf16.Decode(buf)) +} + +func adjustAlign2(r io.Reader, l int) (read int, err error) { + if l&1 != 0 { + var b [1]byte + return r.Read(b[:]) + } + return 0, nil +} + +func adjustAlign4(r io.Reader, l int) (read int, err error) { + if gap := l & 3; gap > 0 { + var b [4]byte + return r.Read(b[:4-gap]) + } + return 0, nil +} + +func discard(r io.Reader, skip int) (read int, err error) { + type discarder interface { + Discard(n int) (discarded int, err error) + } + switch rr := r.(type) { + case discarder: + return rr.Discard(skip) + case io.Seeker: + if _, err = rr.Seek(int64(skip), 1); err != nil { + return 0, err + } + return skip, nil + default: + return io.ReadFull(r, make([]byte, skip)) + } +} + +func readPascalString(r io.Reader) (str string, read int, err error) { + b := make([]byte, 1) + if _, err := io.ReadFull(r, b); err != nil { + return "", 0, err + } + if b[0] == 0 { + return "", 1, nil + } + buf := make([]byte, b[0]) + if _, err := io.ReadFull(r, buf); err != nil { + return "", 1, err + } + return string(buf), len(buf) + 1, nil +} + +func reportReaderPosition(format string, r io.Reader) error { + sk, ok := r.(io.Seeker) + if !ok { + return nil + } + + pos, err := sk.Seek(0, 1) + if err != nil { + return err + } + Debug.Printf(format, pos) + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0e358cd560..772ff6e5f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -159,6 +159,8 @@ github.com/google/btree # github.com/google/uuid v1.1.1 ## explicit github.com/google/uuid +# github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 +github.com/gopherjs/gopherjs/js # github.com/gorilla/handlers v1.4.2 ## explicit github.com/gorilla/handlers @@ -360,6 +362,10 @@ github.com/olivere/elastic/uritemplates ## explicit # github.com/onsi/gomega v1.9.0 ## explicit +# github.com/oov/psd v0.0.0-20200705094106-99303fb2511f +## explicit +github.com/oov/psd +github.com/oov/psd/color # github.com/opentracing/opentracing-go v1.1.0 ## explicit github.com/opentracing/opentracing-go