Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
314 lines
8.2 KiB
Go
314 lines
8.2 KiB
Go
package psd
|
|
|
|
import (
|
|
"errors"
|
|
"image"
|
|
"image/color"
|
|
"io"
|
|
)
|
|
|
|
// logger is subset of log.Logger.
|
|
type logger interface {
|
|
Printf(format string, v ...interface{})
|
|
Println(v ...interface{})
|
|
}
|
|
|
|
// Debug is useful for debugging.
|
|
//
|
|
// You can use by performing the following steps.
|
|
// psd.Debug = log.New(os.Stdout, "psd: ", log.Lshortfile)
|
|
var Debug logger
|
|
|
|
const (
|
|
headerSignature = "8BPS"
|
|
sectionSignature = "8BIM"
|
|
)
|
|
|
|
// AdditionalInfoKey represents the key of the additional layer information.
|
|
type AdditionalInfoKey string
|
|
|
|
// LenSize returns bytes of the length for this key.
|
|
func (a AdditionalInfoKey) LenSize(largeDocument bool) int {
|
|
if largeDocument {
|
|
// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546
|
|
// (**PSB**, the following keys have a length count of 8 bytes:
|
|
// LMsk, Lr16, Lr32, Layr, Mt16, Mt32, Mtrn, Alph, FMsk, lnk2, FEid, FXid, PxSD.
|
|
switch a {
|
|
case "LMsk",
|
|
"Lr16",
|
|
"Lr32",
|
|
"Layr",
|
|
"Mt16",
|
|
"Mt32",
|
|
"Mtrn",
|
|
"Alph",
|
|
"FMsk",
|
|
"lnk2",
|
|
"FEid",
|
|
"FXid",
|
|
"PxSD":
|
|
return 8
|
|
}
|
|
}
|
|
return 4
|
|
}
|
|
|
|
// These keys are defined in this document.
|
|
//
|
|
// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546
|
|
const (
|
|
AdditionalInfoKeyLayerInfo = AdditionalInfoKey("Layr")
|
|
AdditionalInfoKeyLayerInfo16 = AdditionalInfoKey("Lr16")
|
|
AdditionalInfoKeyLayerInfo32 = AdditionalInfoKey("Lr32")
|
|
AdditionalInfoKeyUnicodeLayerName = AdditionalInfoKey("luni")
|
|
AdditionalInfoKeyBlendClippingElements = AdditionalInfoKey("clbl")
|
|
AdditionalInfoKeySectionDividerSetting = AdditionalInfoKey("lsct")
|
|
AdditionalInfoKeySectionDividerSetting2 = AdditionalInfoKey("lsdk")
|
|
)
|
|
|
|
// Config represents Photoshop image file configuration.
|
|
type Config struct {
|
|
Version int
|
|
Rect image.Rectangle
|
|
Channels int
|
|
Depth int // 1 or 8 or 16 or 32
|
|
ColorMode ColorMode
|
|
ColorModeData []byte
|
|
Res map[int]ImageResource
|
|
}
|
|
|
|
// PSB returns whether image is large document format.
|
|
func (cfg *Config) PSB() bool {
|
|
return cfg.Version == 2
|
|
}
|
|
|
|
// Palette returns Palette if any.
|
|
func (cfg *Config) Palette() color.Palette {
|
|
if cfg.ColorMode != ColorModeIndexed {
|
|
return nil
|
|
}
|
|
|
|
// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_38034
|
|
// 0x0417(1046) | (Photoshop 6.0) Transparency Index.
|
|
// 2 bytes for the index of transparent color, if any.
|
|
transparentColorIndex := -1
|
|
if r, ok := cfg.Res[0x0417]; ok {
|
|
transparentColorIndex = int(readUint16(r.Data, 0))
|
|
}
|
|
pal := make(color.Palette, len(cfg.ColorModeData)/3)
|
|
for i := range pal {
|
|
c := color.NRGBA{
|
|
cfg.ColorModeData[i],
|
|
cfg.ColorModeData[i+256],
|
|
cfg.ColorModeData[i+512],
|
|
0xFF,
|
|
}
|
|
if i == transparentColorIndex {
|
|
c.A = 0
|
|
}
|
|
pal[i] = c
|
|
}
|
|
return pal
|
|
}
|
|
|
|
func (cfg *Config) makePicker() (picker, error) {
|
|
var pk picker
|
|
switch cfg.ColorMode {
|
|
case ColorModeIndexed:
|
|
pk = &pickerPalette{Palette: cfg.Palette()}
|
|
case ColorModeBitmap:
|
|
pk = &pickerGray1{}
|
|
default:
|
|
pk = findPicker(cfg.Depth, cfg.ColorMode, cfg.Channels > 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)
|
|
}
|