├── PhotoShopFileType
├── gmcs.rsp
├── smcs.rsp
├── PsdFile
│ ├── Exceptions.cs
│ ├── Layers
│ │ ├── LayerInfo
│ │ │ ├── LayerUnicodeName.cs
│ │ │ ├── RawLayerInfo.cs
│ │ │ └── LayerSectionInfo.cs
│ │ ├── BlendingRanges.cs
│ │ ├── LayerInfo.cs
│ │ ├── Mask.cs
│ │ ├── Layer.cs
│ │ └── Channel.cs
│ ├── RleRowLengths.cs
│ ├── ImageResources
│ │ ├── RawImageResource.cs
│ │ ├── UnicodeAlphaNames.cs
│ │ ├── VersionInfo.cs
│ │ ├── AlphaChannelNames.cs
│ │ ├── Thumbnail.cs
│ │ └── ResolutionInfo.cs
│ ├── PsdBlendMode.cs
│ ├── PsdBlockLengthWriter.cs
│ ├── RleReader.cs
│ ├── PsdBinaryReader.cs
│ ├── PsdBinaryWriter.cs
│ ├── RleWriter.cs
│ ├── ImageResource.cs
│ ├── Util.cs
│ └── PsdFile.cs
├── Properties
│ └── AssemblyInfo.cs
├── PhotoShop.csproj
├── Resources.resx
└── Editor
│ └── PSDEditorWindow.cs
├── .gitattributes
├── PSDPlugin.sln
├── .gitignore
├── README.md
└── License.txt
/PhotoShopFileType/gmcs.rsp:
--------------------------------------------------------------------------------
1 | -unsafe
2 |
--------------------------------------------------------------------------------
/PhotoShopFileType/smcs.rsp:
--------------------------------------------------------------------------------
1 | -unsafe
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/PSDPlugin.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoShop", "PhotoShopFileType\PhotoShop.csproj", "{A04EEDD9-E164-4941-9846-722ACF2FCCA1}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Release|Any CPU = Release|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {A04EEDD9-E164-4941-9846-722ACF2FCCA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {A04EEDD9-E164-4941-9846-722ACF2FCCA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {A04EEDD9-E164-4941-9846-722ACF2FCCA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {A04EEDD9-E164-4941-9846-722ACF2FCCA1}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Exceptions.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is perovided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2012 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Collections.Generic;
16 | using System.Diagnostics;
17 | using System.Linq;
18 | using System.Text;
19 |
20 | namespace PhotoshopFile
21 | {
22 | [Serializable]
23 | public class PsdInvalidException : Exception
24 | {
25 | public PsdInvalidException()
26 | {
27 | }
28 |
29 | public PsdInvalidException(string message)
30 | : base(message)
31 | {
32 | }
33 | }
34 |
35 | [Serializable]
36 | public class RleException : Exception
37 | {
38 | public RleException() { }
39 |
40 | public RleException(string message) : base(message) { }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/LayerInfo/LayerUnicodeName.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 |
16 | namespace PhotoshopFile
17 | {
18 | public class LayerUnicodeName : LayerInfo
19 | {
20 | public override string Key
21 | {
22 | get { return "luni"; }
23 | }
24 |
25 | public string Name { get; set; }
26 |
27 | public LayerUnicodeName(string name)
28 | {
29 | Name = name;
30 | }
31 |
32 | public LayerUnicodeName(PsdBinaryReader reader)
33 | {
34 | Name = reader.ReadUnicodeString();
35 | }
36 |
37 | protected override void WriteData(PsdBinaryWriter writer)
38 | {
39 | var startPosition = writer.BaseStream.Position;
40 |
41 | writer.WriteUnicodeString(Name);
42 | writer.WritePadding(startPosition, 4);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/LayerInfo/RawLayerInfo.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Diagnostics;
16 |
17 | namespace PhotoshopFile
18 | {
19 | [DebuggerDisplay("Layer Info: { key }")]
20 | public class RawLayerInfo : LayerInfo
21 | {
22 | private string key;
23 | public override string Key
24 | {
25 | get { return key; }
26 | }
27 |
28 | public byte[] Data { get; private set; }
29 |
30 | public RawLayerInfo(string key)
31 | {
32 | this.key = key;
33 | }
34 |
35 | public RawLayerInfo(PsdBinaryReader reader, string key, int dataLength)
36 | {
37 | this.key = key;
38 | Data = reader.ReadBytes((int)dataLength);
39 | }
40 |
41 | protected override void WriteData(PsdBinaryWriter writer)
42 | {
43 | writer.Write(Data);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/RleRowLengths.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Linq;
16 |
17 | namespace PhotoshopFile
18 | {
19 | public class RleRowLengths
20 | {
21 | public int[] Values { get; private set; }
22 |
23 | public int Total
24 | {
25 | get { return Values.Sum(); }
26 | }
27 |
28 | public int this[int i]
29 | {
30 | get { return Values[i]; }
31 | set { Values[i] = value; }
32 | }
33 |
34 | public RleRowLengths(int rowCount)
35 | {
36 | Values = new int[rowCount];
37 | }
38 |
39 | public RleRowLengths(PsdBinaryReader reader, int rowCount)
40 | : this(rowCount)
41 | {
42 | for (int i = 0; i < rowCount; i++)
43 | {
44 | Values[i] = reader.ReadUInt16();
45 | }
46 | }
47 |
48 | public void Write(PsdBinaryWriter writer)
49 | {
50 | for (int i = 0; i < Values.Length; i++)
51 | {
52 | writer.Write((UInt16)Values[i]);
53 | }
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResources/RawImageResource.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.IO;
16 |
17 | namespace PhotoshopFile
18 | {
19 | ///
20 | /// Stores the raw data for unimplemented image resource types.
21 | ///
22 | public class RawImageResource : ImageResource
23 | {
24 | public byte[] Data { get; private set; }
25 |
26 | private ResourceID id;
27 | public override ResourceID ID
28 | {
29 | get { return id; }
30 | }
31 |
32 | public RawImageResource(ResourceID resourceId, string name)
33 | : base(name)
34 | {
35 | this.id = resourceId;
36 | }
37 |
38 | public RawImageResource(PsdBinaryReader reader, string signature,
39 | ResourceID resourceId, string name, int numBytes)
40 | : base(name)
41 | {
42 | this.Signature = signature;
43 | this.id = resourceId;
44 | Data = reader.ReadBytes(numBytes);
45 | }
46 |
47 | protected override void WriteData(PsdBinaryWriter writer)
48 | {
49 | writer.Write(Data);
50 | }
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Visual Studio
3 | #################
4 |
5 | ## Ignore Visual Studio temporary files, build results, and
6 | ## files generated by popular Visual Studio add-ons.
7 |
8 | # User-specific files
9 | *.suo
10 | *.user
11 | *.sln.docstates
12 |
13 | # Build results
14 | Build/*.zip
15 | [Dd]ebug/
16 | [Rr]elease/
17 | *_i.c
18 | *_p.c
19 | *.ilk
20 | *.meta
21 | *.obj
22 | *.pch
23 | *.pdb
24 | *.pgc
25 | *.pgd
26 | *.rsp
27 | *.sbr
28 | *.tlb
29 | *.tli
30 | *.tlh
31 | *.tmp
32 | *.vspscc
33 | .builds
34 | *.dotCover
35 |
36 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
37 | #packages/
38 |
39 | # Visual C++ cache files
40 | ipch/
41 | *.aps
42 | *.ncb
43 | *.opensdf
44 | *.sdf
45 |
46 | # Visual Studio profiler
47 | *.psess
48 | *.vsp
49 |
50 | # ReSharper is a .NET coding add-in
51 | _ReSharper*
52 |
53 | # Installshield output folder
54 | [Ee]xpress
55 |
56 | # DocProject is a documentation generator add-in
57 | DocProject/buildhelp/
58 | DocProject/Help/*.HxT
59 | DocProject/Help/*.HxC
60 | DocProject/Help/*.hhc
61 | DocProject/Help/*.hhk
62 | DocProject/Help/*.hhp
63 | DocProject/Help/Html2
64 | DocProject/Help/html
65 |
66 | # Click-Once directory
67 | publish
68 |
69 | # Others
70 | [Bb]in
71 | [Oo]bj
72 | sql
73 | TestResults
74 | *.Cache
75 | ClientBin
76 | stylecop.*
77 | ~$*
78 | *.dbmdl
79 | Generated_Code #added for RIA/Silverlight projects
80 |
81 | # Backup & report files from converting an old project file to a newer
82 | # Visual Studio version. Backup files are not needed, because we have git ;-)
83 | _UpgradeReport_Files/
84 | Backup*/
85 | UpgradeLog*.XML
86 |
87 |
88 |
89 | ############
90 | ## Windows
91 | ############
92 |
93 | # Windows image file caches
94 | Thumbs.db
95 |
96 | # Folder config file
97 | Desktop.ini
98 |
99 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResources/UnicodeAlphaNames.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Collections.Generic;
16 |
17 | namespace PhotoshopFile
18 | {
19 | ///
20 | /// The names of the alpha channels.
21 | ///
22 | public class UnicodeAlphaNames : ImageResource
23 | {
24 | public override ResourceID ID
25 | {
26 | get { return ResourceID.UnicodeAlphaNames; }
27 | }
28 |
29 | private List channelNames = new List();
30 | public List ChannelNames
31 | {
32 | get { return channelNames; }
33 | }
34 |
35 | public UnicodeAlphaNames()
36 | : base(String.Empty)
37 | {
38 | }
39 |
40 | public UnicodeAlphaNames(PsdBinaryReader reader, string name, int resourceDataLength)
41 | : base(name)
42 | {
43 | var endPosition = reader.BaseStream.Position + resourceDataLength;
44 |
45 | while (reader.BaseStream.Position < endPosition)
46 | {
47 | var channelName = reader.ReadUnicodeString();
48 | ChannelNames.Add(channelName);
49 | }
50 | }
51 |
52 | protected override void WriteData(PsdBinaryWriter writer)
53 | {
54 | foreach (var channelName in ChannelNames)
55 | {
56 | writer.WriteUnicodeString(channelName);
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResources/VersionInfo.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2012 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Collections.Generic;
16 | using System.Linq;
17 | using System.Text;
18 |
19 | namespace PhotoshopFile
20 | {
21 | public class VersionInfo : ImageResource
22 | {
23 | public override ResourceID ID
24 | {
25 | get { return ResourceID.VersionInfo; }
26 | }
27 |
28 | public UInt32 Version { get; set; }
29 |
30 | public bool HasRealMergedData { get; set; }
31 |
32 | public string ReaderName { get; set; }
33 |
34 | public string WriterName { get; set; }
35 |
36 | public UInt32 FileVersion { get; set; }
37 |
38 |
39 | public VersionInfo() : base(String.Empty)
40 | {
41 | }
42 |
43 | public VersionInfo(PsdBinaryReader reader, string name)
44 | : base(name)
45 | {
46 | Version = reader.ReadUInt32();
47 | HasRealMergedData = reader.ReadBoolean();
48 | ReaderName = reader.ReadUnicodeString();
49 | WriterName = reader.ReadUnicodeString();
50 | FileVersion = reader.ReadUInt32();
51 | }
52 |
53 | protected override void WriteData(PsdBinaryWriter writer)
54 | {
55 | writer.Write(Version);
56 | writer.Write(HasRealMergedData);
57 | writer.WriteUnicodeString(ReaderName);
58 | writer.WriteUnicodeString(WriterName);
59 | writer.Write(FileVersion);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResources/AlphaChannelNames.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Collections.Generic;
16 |
17 | namespace PhotoshopFile
18 | {
19 | ///
20 | /// The names of the alpha channels
21 | ///
22 | public class AlphaChannelNames : ImageResource
23 | {
24 | public override ResourceID ID
25 | {
26 | get { return ResourceID.AlphaChannelNames; }
27 | }
28 |
29 | private List channelNames = new List();
30 | public List ChannelNames
31 | {
32 | get { return channelNames; }
33 | }
34 |
35 | public AlphaChannelNames() : base(String.Empty)
36 | {
37 | }
38 |
39 | public AlphaChannelNames(PsdBinaryReader reader, string name, int resourceDataLength)
40 | : base(name)
41 | {
42 | var endPosition = reader.BaseStream.Position + resourceDataLength;
43 |
44 | // Alpha channel names are Pascal strings, with no padding in-between.
45 | while (reader.BaseStream.Position < endPosition)
46 | {
47 | var channelName = reader.ReadPascalString(1);
48 | ChannelNames.Add(channelName);
49 | }
50 | }
51 |
52 | protected override void WriteData(PsdBinaryWriter writer)
53 | {
54 | foreach (var channelName in ChannelNames)
55 | {
56 | writer.WritePascalString(channelName, 1);
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/PsdBlendMode.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2012 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Collections.Generic;
16 | using System.Linq;
17 | using System.Text;
18 |
19 | namespace PhotoshopFile
20 | {
21 | public static class PsdBlendMode
22 | {
23 | public const string Normal = "norm";
24 | public const string Darken = "dark";
25 | public const string Lighten = "lite";
26 | public const string Hue = "hue ";
27 | public const string Saturation = "sat ";
28 | public const string Color = "colr";
29 | public const string Luminosity = "lum ";
30 | public const string Multiply = "mul ";
31 | public const string Screen = "scrn";
32 | public const string Dissolve = "diss";
33 | public const string Overlay = "over";
34 | public const string HardLight = "hLit";
35 | public const string SoftLight = "sLit";
36 | public const string Difference = "diff";
37 | public const string Exclusion = "smud";
38 | public const string ColorDodge = "div ";
39 | public const string ColorBurn = "idiv";
40 | public const string LinearBurn = "lbrn";
41 | public const string LinearDodge = "lddg";
42 | public const string VividLight = "vLit";
43 | public const string LinearLight = "lLit";
44 | public const string PinLight = "pLit";
45 | public const string HardMix = "hMix";
46 | public const string PassThrough = "pass";
47 | public const string DarkerColor = "dkCl";
48 | public const string LighterColor = "lgCl";
49 | public const string Subtract = "fsub";
50 | public const string Divide = "fdiv";
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/PhotoShopFileType/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System.Reflection;
15 | using System.Runtime.CompilerServices;
16 | using System.Runtime.InteropServices;
17 |
18 | // General Information about an assembly is controlled through the following
19 | // set of attributes. Change these attribute values to modify the information
20 | // associated with an assembly.
21 | [assembly: AssemblyTitle("PhotoshopFileType")]
22 | [assembly: AssemblyDescription("Photoshop file plug in for Paint.NET")]
23 | [assembly: AssemblyConfiguration("")]
24 | [assembly: AssemblyCompany("")]
25 | [assembly: AssemblyProduct("Paint.NET PSD Plugin")]
26 | [assembly: AssemblyCopyright("Copyright © 2006-2013 Frank Blumenberg and Tao Yue")]
27 | [assembly: AssemblyTrademark("")]
28 | [assembly: AssemblyCulture("")]
29 |
30 | // Setting ComVisible to false makes the types in this assembly not visible
31 | // to COM components. If you need to access a type in this assembly from
32 | // COM, set the ComVisible attribute to true on that type.
33 | [assembly: ComVisible(false)]
34 |
35 | // The following GUID is for the ID of the typelib if this project is exposed to COM
36 | [assembly: Guid("70ed8329-8302-41ac-9714-378e220b2591")]
37 |
38 | // Version information for an assembly consists of the following four values:
39 | //
40 | // Major Version
41 | // Minor Version
42 | // Build Number
43 | // Revision
44 | //
45 | // You can specify all the values or you can default the Revision and Build Numbers
46 | // by using the '*' as shown below:
47 | [assembly: AssemblyVersion("2.4.0.*")]
48 | [assembly: AssemblyFileVersion("2.4.0.0")]
49 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/PsdBlockLengthWriter.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2012 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.IO;
19 | using System.Text;
20 |
21 | namespace PhotoshopFile
22 | {
23 | ///
24 | /// Writes the actual length in front of the data block upon disposal.
25 | ///
26 | class PsdBlockLengthWriter : IDisposable
27 | {
28 | private bool disposed = false;
29 |
30 | long lengthPosition;
31 | long startPosition;
32 | PsdBinaryWriter writer;
33 |
34 | public PsdBlockLengthWriter(PsdBinaryWriter writer)
35 | {
36 | this.writer = writer;
37 |
38 | // Store position so that we can return to it when the length is known.
39 | lengthPosition = writer.BaseStream.Position;
40 |
41 | // Write a sentinel value as a placeholder for the length.
42 | writer.Write((UInt32)0xFEEDFEED);
43 |
44 | // Store the start position of the data block so that we can calculate
45 | // its length when we're done writing.
46 | startPosition = writer.BaseStream.Position;
47 | }
48 |
49 | public void Write()
50 | {
51 | var endPosition = writer.BaseStream.Position;
52 |
53 | writer.BaseStream.Position = lengthPosition;
54 | long length = endPosition - startPosition;
55 | writer.Write((UInt32)length);
56 |
57 | writer.BaseStream.Position = endPosition;
58 | }
59 |
60 | public void Dispose()
61 | {
62 | if (!this.disposed)
63 | {
64 | Write();
65 | this.disposed = true;
66 | }
67 | }
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/BlendingRanges.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Diagnostics;
19 | using System.Globalization;
20 |
21 | namespace PhotoshopFile
22 | {
23 | public class BlendingRanges
24 | {
25 | ///
26 | /// The layer to which this channel belongs
27 | ///
28 | public Layer Layer { get; private set; }
29 |
30 | public byte[] Data { get; set; }
31 |
32 | ///////////////////////////////////////////////////////////////////////////
33 |
34 | public BlendingRanges(Layer layer)
35 | {
36 | Layer = layer;
37 | Data = new byte[0];
38 | }
39 |
40 | ///////////////////////////////////////////////////////////////////////////
41 |
42 | public BlendingRanges(PsdBinaryReader reader, Layer layer)
43 | {
44 | Debug.WriteLine("BlendingRanges started at " + reader.BaseStream.Position.ToString(CultureInfo.InvariantCulture));
45 |
46 | Layer = layer;
47 | var dataLength = reader.ReadInt32();
48 | if (dataLength <= 0)
49 | return;
50 |
51 | Data = reader.ReadBytes(dataLength);
52 | }
53 |
54 | ///////////////////////////////////////////////////////////////////////////
55 |
56 | public void Save(PsdBinaryWriter writer)
57 | {
58 | Debug.WriteLine("BlendingRanges Save started at " + writer.BaseStream.Position.ToString(CultureInfo.InvariantCulture));
59 |
60 | if (Data == null)
61 | {
62 | writer.Write((UInt32)0);
63 | return;
64 | }
65 |
66 | writer.Write((UInt32)Data.Length);
67 | writer.Write(Data);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Unity Psd Importer
2 | ==================
3 |
4 | Unity Psd Importer is an addon for Unity3D. It provides an editor window from which individual layers can be selected
5 | and exported. The layers are exported either as individual PNGs or as an atlas image.
6 |
7 | To compile the Unity PSD Importer in the Unity3D Editor you must have the files gmcs.rsp and smcs.rsp with "-unsafe" in the first line of the files in the root directory of your project. Otherwise you can compile the code as a DLL and it will also work in the editor.
8 |
9 | To use the Unity PSD Importer, in the Unity3D Editor go to Sprites > PSD Import to access the importer, then drag and drop or search for the PSD file you wish to import.
10 |
11 | To only export the layers to PNG files, click on the "Export Visible Layers" button and it will only create the PNG files with the sprites that are visible. You can individually check which layers you want to import.
12 |
13 | To create a root gameobject with all the layers as child sprites and the layers compiled in an atlas, click on the "Create atlas" button. All the layers will be saved to an atlas texture with sprites as children that is autogenerated with the atlas size you define under "Max. atlas size". A root gameobject with the filename of the PSD file is created and all the layers as sprites will be as children gameobjects under the root gameobject.
14 |
15 | To create PNG files individually with each sprite assigned to the PNG files, click on the "Create sprites" button. This will create a root gameobject with the filename of the PSD file and all the layers as sprites under the root gameobject.
16 |
17 | All sprites are created with a center pivot so that they will import in their correct alignment with the PSD file layers' positions.
18 |
19 | Some notes on the PSD support:
20 |
21 | It should support all image layers, however text, layer groups, or other special layers will not be supported. It is best to flatten layer groups and text before importing.
22 |
23 | All the imported layers should be imported at their same positions as in the PSD file.
24 |
25 | When importing as an atlas, if the layers cannot collectively fit in the atlas they will be scaled down to fit the atlas texture, therefore it is preferable to import the layers using "Create sprites" to have the layers imported as separate PNG files.
26 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/LayerInfo/LayerSectionInfo.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 |
16 | namespace PhotoshopFile
17 | {
18 | public enum LayerSectionType
19 | {
20 | Layer = 0,
21 | OpenFolder = 1,
22 | ClosedFolder = 2,
23 | SectionDivider = 3
24 | }
25 |
26 | public enum LayerSectionSubtype
27 | {
28 | Normal = 0,
29 | SceneGroup = 1
30 | }
31 |
32 | ///
33 | /// Layer sections are known as Groups in the Photoshop UI.
34 | ///
35 | public class LayerSectionInfo : LayerInfo
36 | {
37 | private string key;
38 | public override string Key
39 | {
40 | get { return key; }
41 | }
42 |
43 | public LayerSectionType SectionType { get; set; }
44 |
45 | private LayerSectionSubtype? subtype;
46 | public LayerSectionSubtype Subtype
47 | {
48 | get { return subtype ?? LayerSectionSubtype.Normal; }
49 | set { subtype = value; }
50 | }
51 |
52 | private string blendModeKey;
53 | public string BlendModeKey
54 | {
55 | get { return blendModeKey; }
56 | set
57 | {
58 | if (value.Length != 4)
59 | throw new ArgumentException("Blend mode key must have a length of 4.");
60 | blendModeKey = value;
61 | }
62 | }
63 |
64 | public LayerSectionInfo(PsdBinaryReader reader, string key, int dataLength)
65 | {
66 | // The key for layer section info is documented to be "lsct". However,
67 | // some Photoshop files use the undocumented key "lsdk", with apparently
68 | // the same data format.
69 | this.key = key;
70 |
71 | SectionType = (LayerSectionType)reader.ReadInt32();
72 | if (dataLength >= 12)
73 | {
74 | var signature = reader.ReadAsciiChars(4);
75 | if (signature != "8BIM")
76 | throw new PsdInvalidException("Invalid section divider signature.");
77 |
78 | BlendModeKey = reader.ReadAsciiChars(4);
79 | if (dataLength >= 16)
80 | {
81 | Subtype = (LayerSectionSubtype)reader.ReadInt32();
82 | }
83 | }
84 | }
85 |
86 | protected override void WriteData(PsdBinaryWriter writer)
87 | {
88 | writer.Write((Int32)SectionType);
89 | if (BlendModeKey != null)
90 | {
91 | writer.WriteAsciiChars("8BIM");
92 | writer.WriteAsciiChars(BlendModeKey);
93 | if (subtype != null)
94 | writer.Write((Int32)Subtype);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResources/Thumbnail.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.IO;
19 | using System.Diagnostics;
20 | using UnityEngine;
21 |
22 | namespace PhotoshopFile {
23 | ///
24 | /// Summary description for Thumbnail.
25 | ///
26 | public class Thumbnail : RawImageResource {
27 | public Texture2D Image { get; private set; }
28 |
29 | public Thumbnail(ResourceID id, string name)
30 | : base(id, name) {
31 | }
32 |
33 | public Thumbnail(PsdBinaryReader psdReader, ResourceID id, string name, int numBytes)
34 | : base(psdReader, "8BIM", id, name, numBytes) {
35 | using (var memoryStream = new MemoryStream(Data))
36 | using (var reader = new PsdBinaryReader(memoryStream, psdReader)) {
37 | const int HEADER_LENGTH = 28;
38 | var format = reader.ReadUInt32();
39 | var width = reader.ReadUInt32();
40 | var height = reader.ReadUInt32();
41 | var widthBytes = reader.ReadUInt32();
42 | var size = reader.ReadUInt32();
43 | var compressedSize = reader.ReadUInt32();
44 | var bitPerPixel = reader.ReadUInt16();
45 | var planes = reader.ReadUInt16();
46 |
47 | // Raw RGB bitmap
48 | if (format == 0) {
49 | Image = new Texture2D((int)width, (int)height, TextureFormat.RGB24, true);
50 | }
51 | // JPEG bitmap
52 | else if (format == 1) {
53 | byte[] imgData = reader.ReadBytes(numBytes - HEADER_LENGTH);
54 | Image = new Texture2D((int)width, (int)height, TextureFormat.RGB24, true);
55 | Image.LoadImage(imgData);
56 |
57 | // Reverse BGR pixels from old thumbnail format
58 | if (id == ResourceID.ThumbnailBgr) {
59 | //for(int y=0;y
34 | /// Decodes a PackBits RLE stream.
35 | ///
36 | /// Output buffer for decoded data.
37 | /// Offset at which to begin writing.
38 | /// Number of bytes to decode from the stream.
39 | unsafe public int Read(byte[] buffer, int offset, int count)
40 | {
41 | if (!Util.CheckBufferBounds(buffer, offset, count))
42 | throw new ArgumentOutOfRangeException();
43 |
44 | // Pin the entire buffer now, so that we don't keep pinning and unpinning
45 | // for each RLE packet.
46 | fixed (byte* ptrBuffer = &buffer[0])
47 | {
48 | int bytesLeft = count;
49 | int bufferIdx = offset;
50 | while (bytesLeft > 0)
51 | {
52 | // ReadByte returns an unsigned byte, but we want a signed byte.
53 | var flagCounter = unchecked((sbyte)stream.ReadByte());
54 |
55 | // Raw packet
56 | if (flagCounter > 0)
57 | {
58 | var readLength = flagCounter + 1;
59 | if (bytesLeft < readLength)
60 | throw new RleException("Raw packet overruns the decode window.");
61 |
62 | stream.Read(buffer, bufferIdx, readLength);
63 |
64 | bufferIdx += readLength;
65 | bytesLeft -= readLength;
66 | }
67 | // RLE packet
68 | else if (flagCounter > -128)
69 | {
70 | var runLength = 1 - flagCounter;
71 | var byteValue = (byte)stream.ReadByte();
72 | if (runLength > bytesLeft)
73 | throw new RleException("RLE packet overruns the decode window.");
74 |
75 | byte* ptr = ptrBuffer + bufferIdx;
76 | byte* ptrEnd = ptr + runLength;
77 | while (ptr < ptrEnd)
78 | {
79 | *ptr = byteValue;
80 | ptr++;
81 | }
82 |
83 | bufferIdx += runLength;
84 | bytesLeft -= runLength;
85 | }
86 | else
87 | {
88 | // The canonical PackBits algorithm will never emit 0x80 (-128), but
89 | // some programs do. Simply skip over the byte.
90 | }
91 | }
92 |
93 | Debug.Assert(bytesLeft == 0);
94 | return count - bytesLeft;
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResources/ResolutionInfo.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2012 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 |
19 | namespace PhotoshopFile
20 | {
21 | ///
22 | /// Summary description for ResolutionInfo.
23 | ///
24 | public class ResolutionInfo : ImageResource
25 | {
26 | public override ResourceID ID
27 | {
28 | get { return ResourceID.ResolutionInfo; }
29 | }
30 |
31 | ///
32 | /// Horizontal DPI.
33 | ///
34 | public UFixed16_16 HDpi { get; set; }
35 |
36 | ///
37 | /// Vertical DPI.
38 | ///
39 | public UFixed16_16 VDpi { get; set; }
40 |
41 | ///
42 | /// 1 = pixels per inch, 2 = pixels per centimeter
43 | ///
44 | public enum ResUnit
45 | {
46 | PxPerInch = 1,
47 | PxPerCm = 2
48 | }
49 |
50 | ///
51 | /// Display units for horizontal resolution. This only affects the
52 | /// user interface; the resolution is still stored in the PSD file
53 | /// as pixels/inch.
54 | ///
55 | public ResUnit HResDisplayUnit { get; set; }
56 |
57 | ///
58 | /// Display units for vertical resolution.
59 | ///
60 | public ResUnit VResDisplayUnit { get; set; }
61 |
62 | ///
63 | /// Physical units.
64 | ///
65 | public enum Unit
66 | {
67 | Inches = 1,
68 | Centimeters = 2,
69 | Points = 3,
70 | Picas = 4,
71 | Columns = 5
72 | }
73 |
74 | public Unit WidthDisplayUnit { get; set; }
75 |
76 | public Unit HeightDisplayUnit { get; set; }
77 |
78 | public ResolutionInfo() : base(String.Empty)
79 | {
80 | }
81 |
82 | public ResolutionInfo(PsdBinaryReader reader, string name)
83 | : base(name)
84 | {
85 | this.HDpi = new UFixed16_16(reader.ReadUInt32());
86 | this.HResDisplayUnit = (ResUnit)reader.ReadInt16();
87 | this.WidthDisplayUnit = (Unit)reader.ReadInt16();
88 |
89 | this.VDpi = new UFixed16_16(reader.ReadUInt32());
90 | this.VResDisplayUnit = (ResUnit)reader.ReadInt16();
91 | this.HeightDisplayUnit = (Unit)reader.ReadInt16();
92 | }
93 |
94 | protected override void WriteData(PsdBinaryWriter writer)
95 | {
96 | writer.Write(HDpi.Integer);
97 | writer.Write(HDpi.Fraction);
98 | writer.Write((Int16)HResDisplayUnit);
99 | writer.Write((Int16)WidthDisplayUnit);
100 |
101 | writer.Write(VDpi.Integer);
102 | writer.Write(VDpi.Fraction);
103 | writer.Write((Int16)VResDisplayUnit);
104 | writer.Write((Int16)HeightDisplayUnit);
105 | }
106 |
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 |
3 | Photoshop PSD FileType Plugin for Paint.NET
4 | http://psdplugin.codeplex.com/
5 |
6 | Copyright (c) 2006-2007 Frank Blumenberg
7 | Copyright (c) 2010-2013 Tao Yue
8 |
9 | MIT License: http://www.opensource.org/licenses/mit-license.php
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy
12 | of this software and associated documentation files (the "Software"), to deal
13 | in the Software without restriction, including without limitation the rights
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | copies of the Software, and to permit persons to whom the Software is
16 | furnished to do so, subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in all
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | THE SOFTWARE.
28 |
29 | /////////////////////////////////////////////////////////////////////////////////
30 |
31 | Portions of the software have been adapted from Yet Another PSD Parser:
32 | http://www.codeproject.com/KB/graphics/PSDParser.aspx
33 |
34 | These portions are provided under the BSD License:
35 | http://www.opensource.org/licenses/BSD-3-Clause
36 |
37 | ----
38 |
39 | Copyright (c) 2006, Jonas Beckeman
40 | All rights reserved.
41 |
42 | Redistribution and use in source and binary forms, with or without
43 | modification, are permitted provided that the following conditions are met:
44 |
45 | * Redistributions of source code must retain the above copyright
46 | notice, this list of conditions and the following disclaimer.
47 | * Redistributions in binary form must reproduce the above copyright
48 | notice, this list of conditions and the following disclaimer in the
49 | documentation and/or other materials provided with the distribution.
50 | * Neither the name of Jonas Beckeman nor the names of its contributors
51 | may be used to endorse or promote products derived from this software
52 | without specific prior written permission.
53 |
54 | THIS SOFTWARE IS PROVIDED BY JONAS BECKEMAN AND CONTRIBUTORS ``AS IS'' AND ANY
55 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
56 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
57 | DISCLAIMED. IN NO EVENT SHALL JONAS BECKEMAN AND CONTRIBUTORS BE LIABLE FOR ANY
58 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
59 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
60 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
61 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
62 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
63 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64 |
65 | /////////////////////////////////////////////////////////////////////////////////
66 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PhotoShop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.50727
7 | 2.0
8 | {A04EEDD9-E164-4941-9846-722ACF2FCCA1}
9 | Library
10 | Properties
11 | PhotoShop
12 | PhotoShop
13 | false
14 |
15 |
16 | v3.5
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 |
28 |
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 | true
36 | true
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | C:\Program Files (x86)\Unity\Editor\Data\Managed\UnityEditor.dll
45 |
46 |
47 | C:\Program Files (x86)\Unity\Editor\Data\Managed\UnityEngine.dll
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | Resources.Designer.cs
82 | PreserveNewest
83 | PaintDotNet.Data.PhotoshopFileType
84 |
85 |
86 |
87 |
88 |
89 | xcopy "$(TargetPath)" "z:\Unity\projects\PSDFiles\Assets\PSDSprites\Scripts\" /Y /R
90 |
91 |
98 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/Mask.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Collections.Specialized;
19 | using System.Diagnostics;
20 | using System.Globalization;
21 | using UnityEngine;
22 |
23 | namespace PhotoshopFile
24 | {
25 | public class Mask
26 | {
27 | ///
28 | /// The layer to which this mask belongs
29 | ///
30 | public Layer Layer { get; private set; }
31 |
32 | ///
33 | /// The rectangle enclosing the mask.
34 | ///
35 | public Rect Rect { get; set; }
36 |
37 | private byte backgroundColor;
38 | public byte BackgroundColor
39 | {
40 | get { return backgroundColor; }
41 | set
42 | {
43 | if ((value != 0) && (value != 255))
44 | throw new PsdInvalidException("Mask background must be fully-opaque or fully-transparent.");
45 | backgroundColor = value;
46 | }
47 | }
48 |
49 | private static int positionVsLayerBit = BitVector32.CreateMask();
50 | private static int disabledBit = BitVector32.CreateMask(positionVsLayerBit);
51 | private static int invertOnBlendBit = BitVector32.CreateMask(disabledBit);
52 |
53 | private BitVector32 flags;
54 | public BitVector32 Flags { get { return flags; } }
55 |
56 | ///
57 | /// If true, the position of the mask is relative to the layer.
58 | ///
59 | public bool PositionVsLayer
60 | {
61 | get { return flags[positionVsLayerBit]; }
62 | set { flags[positionVsLayerBit] = value; }
63 | }
64 |
65 | public bool Disabled
66 | {
67 | get { return flags[disabledBit]; }
68 | set { flags[disabledBit] = value; }
69 | }
70 |
71 | ///
72 | /// if true, invert the mask when blending.
73 | ///
74 | public bool InvertOnBlend
75 | {
76 | get { return flags[invertOnBlendBit]; }
77 | set { flags[invertOnBlendBit] = value; }
78 | }
79 |
80 | ///
81 | /// Mask image data.
82 | ///
83 | public byte[] ImageData { get; set; }
84 |
85 | public Mask(Layer layer)
86 | {
87 | Layer = layer;
88 | this.flags = new BitVector32();
89 | }
90 |
91 | public Mask(Layer layer, Rect rect, byte color, BitVector32 flags)
92 | {
93 | Layer = layer;
94 | Rect = rect;
95 | BackgroundColor = color;
96 | this.flags = flags;
97 | }
98 | }
99 |
100 | ///
101 | /// Mask info for a layer. Contains both the layer and user masks.
102 | ///
103 | public class MaskInfo
104 | {
105 | public Mask LayerMask { get; set; }
106 |
107 | public Mask UserMask { get; set; }
108 |
109 | ///
110 | /// Construct MaskInfo with null masks.
111 | ///
112 | public MaskInfo()
113 | {
114 | }
115 |
116 | public MaskInfo(PsdBinaryReader reader, Layer layer)
117 | {
118 | var maskLength = reader.ReadUInt32();
119 | if (maskLength <= 0)
120 | return;
121 |
122 | var startPosition = reader.BaseStream.Position;
123 | var endPosition = startPosition + maskLength;
124 |
125 | // Read layer mask
126 | var rectangle = reader.ReadRectangle();
127 | var backgroundColor = reader.ReadByte();
128 | var flagsByte = reader.ReadByte();
129 | LayerMask = new Mask(layer, rectangle, backgroundColor, new BitVector32(flagsByte));
130 |
131 | // User mask is supplied separately when there is also a vector mask.
132 | if (maskLength == 36)
133 | {
134 | var userFlagsByte = reader.ReadByte();
135 | var userBackgroundColor = reader.ReadByte();
136 | var userRectangle = reader.ReadRectangle();
137 | UserMask = new Mask(layer, userRectangle, userBackgroundColor,
138 | new BitVector32(userFlagsByte));
139 | }
140 |
141 | // 20-byte mask data will end with padding.
142 | reader.BaseStream.Position = endPosition;
143 | }
144 |
145 | ///////////////////////////////////////////////////////////////////////////
146 |
147 | public void Save(PsdBinaryWriter writer)
148 | {
149 | if (LayerMask == null)
150 | {
151 | writer.Write((UInt32)0);
152 | return;
153 | }
154 |
155 | using (new PsdBlockLengthWriter(writer))
156 | {
157 | writer.Write(LayerMask.Rect);
158 | writer.Write(LayerMask.BackgroundColor);
159 | writer.Write((byte)LayerMask.Flags.Data);
160 |
161 | if (UserMask == null)
162 | {
163 | // Pad by 2 bytes to make the block length 20
164 | writer.Write((UInt16)0);
165 | }
166 | else
167 | {
168 | writer.Write((byte)UserMask.Flags.Data);
169 | writer.Write(UserMask.BackgroundColor);
170 | writer.Write(UserMask.Rect);
171 | }
172 | }
173 | }
174 |
175 | }
176 | }
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/PsdBinaryReader.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.IO;
19 | using System.Text;
20 | using UnityEngine;
21 |
22 | namespace PhotoshopFile
23 | {
24 | ///
25 | /// Reads PSD data types in big-endian byte order.
26 | ///
27 | public class PsdBinaryReader : IDisposable
28 | {
29 | private BinaryReader reader;
30 | private Encoding encoding;
31 |
32 | public Stream BaseStream
33 | {
34 | get { return reader.BaseStream; }
35 | }
36 |
37 | public PsdBinaryReader(Stream stream, PsdBinaryReader reader)
38 | : this (stream, reader.encoding)
39 | {
40 | }
41 |
42 | public PsdBinaryReader(Stream stream, Encoding encoding)
43 | {
44 | this.encoding = encoding;
45 |
46 | // ReadPascalString and ReadUnicodeString handle encoding explicitly.
47 | // BinaryReader.ReadString() is never called, so it is constructed with
48 | // ASCII encoding to make accidental usage obvious.
49 | reader = new BinaryReader(stream, Encoding.ASCII);
50 | }
51 |
52 | public byte ReadByte()
53 | {
54 | return reader.ReadByte();
55 | }
56 |
57 | public byte[] ReadBytes(int count)
58 | {
59 | return reader.ReadBytes(count);
60 | }
61 |
62 | public bool ReadBoolean()
63 | {
64 | return reader.ReadBoolean();
65 | }
66 |
67 | public Int16 ReadInt16()
68 | {
69 | var val = reader.ReadInt16();
70 | unsafe
71 | {
72 | Util.SwapBytes((byte*)&val, 2);
73 | }
74 | return val;
75 | }
76 |
77 | public Int32 ReadInt32()
78 | {
79 | var val = reader.ReadInt32();
80 | unsafe
81 | {
82 | Util.SwapBytes((byte*)&val, 4);
83 | }
84 | return val;
85 | }
86 |
87 | public Int64 ReadInt64()
88 | {
89 | var val = reader.ReadInt64();
90 | unsafe
91 | {
92 | Util.SwapBytes((byte*)&val, 8);
93 | }
94 | return val;
95 | }
96 |
97 | public UInt16 ReadUInt16()
98 | {
99 | var val = reader.ReadUInt16();
100 | unsafe
101 | {
102 | Util.SwapBytes((byte*)&val, 2);
103 | }
104 | return val;
105 | }
106 |
107 | public UInt32 ReadUInt32()
108 | {
109 | var val = reader.ReadUInt32();
110 | unsafe
111 | {
112 | Util.SwapBytes((byte*)&val, 4);
113 | }
114 | return val;
115 | }
116 |
117 | public UInt64 ReadUInt64()
118 | {
119 | var val = reader.ReadUInt64();
120 | unsafe
121 | {
122 | Util.SwapBytes((byte*)&val, 8);
123 | }
124 | return val;
125 | }
126 |
127 | //////////////////////////////////////////////////////////////////
128 |
129 | ///
130 | /// Read padding to get to the byte multiple for the block.
131 | ///
132 | /// Starting position of the padded block.
133 | /// Byte multiple that the block is padded to.
134 | public void ReadPadding(long startPosition, int padMultiple)
135 | {
136 | // Pad to specified byte multiple
137 | var totalLength = reader.BaseStream.Position - startPosition;
138 | var padBytes = Util.GetPadding((int)totalLength, padMultiple);
139 | ReadBytes(padBytes);
140 | }
141 |
142 | public Rect ReadRectangle()
143 | {
144 | var rect = new Rect();
145 | rect.y = ReadInt32();
146 | rect.x = ReadInt32();
147 | rect.height = ReadInt32() - rect.y;
148 | rect.width = ReadInt32() - rect.x;
149 | return rect;
150 | }
151 |
152 | ///
153 | /// Read a fixed-length ASCII string.
154 | ///
155 | public string ReadAsciiChars(int count)
156 | {
157 | var bytes = reader.ReadBytes(count); ;
158 | var s = Encoding.ASCII.GetString(bytes);
159 | return s;
160 | }
161 |
162 | ///
163 | /// Read a Pascal string using the specified encoding.
164 | ///
165 | /// Byte multiple that the Pascal string is padded to.
166 | public string ReadPascalString(int padMultiple)
167 | {
168 | var startPosition = reader.BaseStream.Position;
169 |
170 | byte stringLength = ReadByte();
171 | var bytes = ReadBytes(stringLength);
172 | ReadPadding(startPosition, padMultiple);
173 |
174 | // Default decoder uses best-fit fallback, so it will not throw any
175 | // exceptions if unknown characters are encountered.
176 | var str = encoding.GetString(bytes);
177 | return str;
178 | }
179 |
180 | public string ReadUnicodeString()
181 | {
182 | var numChars = ReadInt32();
183 | var length = 2 * numChars;
184 | var data = ReadBytes(length);
185 | var str = Encoding.BigEndianUnicode.GetString(data, 0, length);
186 |
187 | return str;
188 | }
189 |
190 | //////////////////////////////////////////////////////////////////
191 |
192 | # region IDisposable
193 |
194 | private bool disposed = false;
195 |
196 | public void Dispose()
197 | {
198 | Dispose(true);
199 | GC.SuppressFinalize(this);
200 | }
201 |
202 | protected virtual void Dispose(bool disposing)
203 | {
204 | // Check to see if Dispose has already been called.
205 | if (disposed)
206 | return;
207 |
208 | if (disposing)
209 | {
210 | if (reader != null)
211 | {
212 | // BinaryReader.Dispose() is protected.
213 | reader.Close();
214 | reader = null;
215 | }
216 | }
217 |
218 | disposed = true;
219 | }
220 |
221 | #endregion
222 |
223 | }
224 |
225 | }
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/PsdBinaryWriter.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.IO;
19 | using System.Text;
20 | using UnityEngine;
21 |
22 | namespace PhotoshopFile
23 | {
24 | ///
25 | /// Writes PSD data types in big-endian byte order.
26 | ///
27 | public class PsdBinaryWriter : IDisposable
28 | {
29 | private BinaryWriter writer;
30 | private Encoding encoding;
31 |
32 | public Stream BaseStream
33 | {
34 | get { return writer.BaseStream; }
35 | }
36 |
37 | public bool AutoFlush { get; set; }
38 |
39 | public PsdBinaryWriter(Stream stream, Encoding encoding)
40 | {
41 | this.encoding = encoding;
42 |
43 | // BinaryWriter.Write(String) cannot be used, as it writes a UTF-7
44 | // (variable-sized) length integer, while PSD strings have a fixed-size
45 | // length field. Encoding is set to ASCII to catch any accidental usage.
46 | writer = new BinaryWriter(stream, Encoding.ASCII);
47 | }
48 |
49 | public void Flush()
50 | {
51 | writer.Flush();
52 | }
53 |
54 | public void Write(Rect rect)
55 | {
56 | Write((int)rect.top);
57 | Write((int)rect.left);
58 | Write((int)rect.bottom);
59 | Write((int)rect.right);
60 | }
61 |
62 | ///
63 | /// Pad the length of a block to a multiple.
64 | ///
65 | /// Starting position of the padded block.
66 | /// Byte multiple to pad to.
67 | public void WritePadding(long startPosition, int padMultiple)
68 | {
69 | var length = writer.BaseStream.Position - startPosition;
70 | var padBytes = Util.GetPadding((int)length, padMultiple);
71 | for (long i = 0; i < padBytes; i++)
72 | writer.Write((byte)0);
73 |
74 | if (AutoFlush)
75 | Flush();
76 | }
77 |
78 | ///
79 | /// Write string as ASCII characters, without a length prefix.
80 | ///
81 | public void WriteAsciiChars(string s)
82 | {
83 | var bytes = Encoding.ASCII.GetBytes(s);
84 | writer.Write(bytes);
85 |
86 | if (AutoFlush)
87 | Flush();
88 | }
89 |
90 |
91 | ///
92 | /// Writes a Pascal string using the specified encoding.
93 | ///
94 | /// Unicode string to convert to the encoding.
95 | /// Byte multiple that the Pascal string is padded to.
96 | /// Maximum number of bytes to write.
97 | public void WritePascalString(string s, int padMultiple, byte maxBytes = 255)
98 | {
99 | var startPosition = writer.BaseStream.Position;
100 |
101 | byte[] bytesArray = encoding.GetBytes(s);
102 | if (bytesArray.Length > maxBytes)
103 | {
104 | var tempArray = new byte[maxBytes];
105 | Array.Copy(bytesArray, tempArray, maxBytes);
106 | bytesArray = tempArray;
107 | }
108 |
109 | writer.Write((byte)bytesArray.Length);
110 | writer.Write(bytesArray);
111 | WritePadding(startPosition, padMultiple);
112 | }
113 |
114 | ///
115 | /// Write a Unicode string to the stream.
116 | ///
117 | public void WriteUnicodeString(string s)
118 | {
119 | Write(s.Length);
120 | var data = Encoding.BigEndianUnicode.GetBytes(s);
121 | Write(data);
122 | }
123 |
124 | public void Write(bool value)
125 | {
126 | writer.Write(value);
127 |
128 | if (AutoFlush)
129 | Flush();
130 | }
131 |
132 | public void Write(byte[] value)
133 | {
134 | writer.Write(value);
135 |
136 | if (AutoFlush)
137 | Flush();
138 | }
139 |
140 | public void Write(byte value)
141 | {
142 | writer.Write(value);
143 |
144 | if (AutoFlush)
145 | Flush();
146 | }
147 |
148 | public void Write(Int16 value)
149 | {
150 | unsafe
151 | {
152 | Util.SwapBytes2((byte*)&value);
153 | }
154 | writer.Write(value);
155 |
156 | if (AutoFlush)
157 | Flush();
158 | }
159 |
160 | public void Write(Int32 value)
161 | {
162 | unsafe
163 | {
164 | Util.SwapBytes4((byte*)&value);
165 | }
166 | writer.Write(value);
167 |
168 | if (AutoFlush)
169 | Flush();
170 | }
171 |
172 | public void Write(Int64 value)
173 | {
174 | unsafe
175 | {
176 | Util.SwapBytes((byte*)&value, 8);
177 | }
178 | writer.Write(value);
179 |
180 | if (AutoFlush)
181 | Flush();
182 | }
183 |
184 | public void Write(UInt16 value)
185 | {
186 | unsafe
187 | {
188 | Util.SwapBytes2((byte*)&value);
189 | }
190 | writer.Write(value);
191 |
192 | if (AutoFlush)
193 | Flush();
194 | }
195 |
196 | public void Write(UInt32 value)
197 | {
198 | unsafe
199 | {
200 | Util.SwapBytes4((byte*)&value);
201 | }
202 | writer.Write(value);
203 |
204 | if (AutoFlush)
205 | Flush();
206 | }
207 |
208 | public void Write(UInt64 value)
209 | {
210 | unsafe
211 | {
212 | Util.SwapBytes((byte*)&value, 8);
213 | }
214 | writer.Write(value);
215 |
216 | if (AutoFlush)
217 | Flush();
218 | }
219 |
220 |
221 | //////////////////////////////////////////////////////////////////
222 |
223 | # region IDisposable
224 |
225 | private bool disposed = false;
226 |
227 | public void Dispose()
228 | {
229 | Dispose(true);
230 | GC.SuppressFinalize(this);
231 | }
232 |
233 | protected virtual void Dispose(bool disposing)
234 | {
235 | // Check to see if Dispose has already been called.
236 | if (disposed)
237 | return;
238 |
239 | if (disposing)
240 | {
241 | if (writer != null)
242 | {
243 | // BinaryWriter.Dispose() is protected.
244 | writer.Close();
245 | writer = null;
246 | }
247 | }
248 |
249 | disposed = true;
250 | }
251 |
252 | #endregion
253 |
254 | }
255 | }
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/RleWriter.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Diagnostics;
19 | using System.IO;
20 |
21 |
22 | namespace PhotoshopFile
23 | {
24 | public class RleWriter
25 | {
26 | private int maxPacketLength = 128;
27 |
28 | // Current task
29 | private object rleLock;
30 | private Stream stream;
31 | private byte[] data;
32 | private int offset;
33 |
34 | // Current packet
35 | private bool isRepeatPacket;
36 | private int idxPacketStart;
37 | private int packetLength;
38 |
39 | private byte runValue;
40 | private int runLength;
41 |
42 | public RleWriter(Stream stream)
43 | {
44 | rleLock = new object();
45 | this.stream = stream;
46 | }
47 |
48 | ///
49 | /// Encodes byte data using PackBits RLE compression.
50 | ///
51 | /// Raw data to be encoded.
52 | /// Offset at which to begin transferring data.
53 | /// Number of bytes of data to transfer.
54 | /// Number of compressed bytes written to the stream.
55 | ///
56 | /// There are multiple ways to encode two-byte runs:
57 | /// 1. Apple PackBits only encodes three-byte runs as repeats.
58 | /// 2. Adobe Photoshop encodes two-byte runs as repeats, unless preceded
59 | /// by literals.
60 | /// 3. TIFF PackBits recommends that two-byte runs be encoded as repeats,
61 | /// unless preceded *and* followed by literals.
62 | ///
63 | /// This class adopts the Photoshop behavior, as it has slightly better
64 | /// compression efficiency than Apple PackBits, and is easier to implement
65 | /// than TIFF PackBits.
66 | ///
67 | unsafe public int Write(byte[] data, int offset, int count)
68 | {
69 | if (!Util.CheckBufferBounds(data, offset, count))
70 | throw new ArgumentOutOfRangeException();
71 |
72 | // We cannot encode a count of 0, because the PackBits flag-counter byte
73 | // uses 0 to indicate a length of 1.
74 | if (count == 0)
75 | throw new ArgumentOutOfRangeException("count");
76 |
77 | lock (rleLock)
78 | {
79 | var startPosition = stream.Position;
80 |
81 | this.data = data;
82 | this.offset = offset;
83 | fixed (byte* ptrData = &data[0])
84 | {
85 | byte* ptr = ptrData + offset;
86 | byte* ptrEnd = ptr + count;
87 | var bytesEncoded = EncodeToStream(ptr, ptrEnd);
88 | Debug.Assert(bytesEncoded == count, "Encoded byte count should match the argument.");
89 | }
90 |
91 | return (int)(stream.Position - startPosition);
92 | }
93 | }
94 |
95 | private void ClearPacket()
96 | {
97 | this.isRepeatPacket = false;
98 | this.packetLength = 0;
99 | }
100 |
101 | private void WriteRepeatPacket(int length)
102 | {
103 | var header = unchecked((byte)(1 - length));
104 | stream.WriteByte(header);
105 | stream.WriteByte(runValue);
106 | }
107 |
108 | private void WriteLiteralPacket(int length)
109 | {
110 | var header = unchecked((byte)(length - 1));
111 | stream.WriteByte(header);
112 | stream.Write(data, idxPacketStart, length);
113 | }
114 |
115 | private void WritePacket()
116 | {
117 | if (isRepeatPacket)
118 | WriteRepeatPacket(packetLength);
119 | else
120 | WriteLiteralPacket(packetLength);
121 | }
122 |
123 | private void StartPacket(int count,
124 | bool isRepeatPacket, int runLength, byte value)
125 | {
126 | this.isRepeatPacket = isRepeatPacket;
127 |
128 | this.packetLength = runLength;
129 | this.runLength = runLength;
130 | this.runValue = value;
131 |
132 | this.idxPacketStart = offset + count;
133 | }
134 |
135 | private void ExtendPacketAndRun(byte value)
136 | {
137 | packetLength++;
138 | runLength++;
139 | }
140 |
141 | private void ExtendPacketStartNewRun(byte value)
142 | {
143 | packetLength++;
144 | runLength = 1;
145 | runValue = value;
146 | }
147 |
148 | unsafe private int EncodeToStream(byte* ptr, byte* ptrEnd)
149 | {
150 | // Begin the first packet.
151 | StartPacket(0, false, 1, *ptr);
152 | int numBytesEncoded = 1;
153 | ptr++;
154 |
155 | // Loop invariant: Packet is never empty.
156 | while (ptr < ptrEnd)
157 | {
158 | var value = *ptr;
159 |
160 | if (packetLength == 1)
161 | {
162 | isRepeatPacket = (value == runValue);
163 | if (isRepeatPacket)
164 | ExtendPacketAndRun(value);
165 | else
166 | ExtendPacketStartNewRun(value);
167 | }
168 | else if (packetLength == maxPacketLength)
169 | {
170 | // Packet is full, so write it out and start a new one.
171 | WritePacket();
172 | StartPacket(numBytesEncoded, false, 1, value);
173 | }
174 | else if (isRepeatPacket)
175 | {
176 | // Decide whether to continue the repeat packet.
177 | if (value == runValue)
178 | ExtendPacketAndRun(value);
179 | else
180 | {
181 | // Different color, so terminate the run and start a new packet.
182 | WriteRepeatPacket(packetLength);
183 | StartPacket(numBytesEncoded, false, 1, value);
184 | }
185 | }
186 | else
187 | {
188 | // Decide whether to continue the literal packet.
189 | if (value == runValue)
190 | {
191 | ExtendPacketAndRun(value);
192 |
193 | // A 3-byte run terminates the literal and starts a new repeat
194 | // packet. That's because the 3-byte run can be encoded as a
195 | // 2-byte repeat. So even if the run ends at 3, we've already
196 | // paid for the next flag-counter byte.
197 | if (runLength == 3)
198 | {
199 | // The 3-byte run can come in the middle of a literal packet,
200 | // but not at the beginning. The first 2 bytes of the run
201 | // should've triggered a repeat packet.
202 | Debug.Assert(packetLength > 3);
203 |
204 | // -2 because numBytesEncoded has not yet been incremented
205 | WriteLiteralPacket(packetLength - 3);
206 | StartPacket(numBytesEncoded - 2, true, 3, value);
207 | }
208 | }
209 | else
210 | {
211 | ExtendPacketStartNewRun(value);
212 | }
213 | }
214 |
215 | ptr++;
216 | numBytesEncoded++;
217 | }
218 |
219 | // Loop terminates with a non-empty packet waiting to be written out.
220 | WritePacket();
221 | ClearPacket();
222 |
223 | return numBytesEncoded;
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/ImageResource.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Collections.Generic;
19 | using System.Diagnostics;
20 | using System.Globalization;
21 | using System.IO;
22 | using System.Linq;
23 |
24 | namespace PhotoshopFile
25 | {
26 | public enum ResourceID
27 | {
28 | Undefined = 0,
29 | MacPrintInfo = 1001,
30 | ResolutionInfo = 1005,
31 | AlphaChannelNames = 1006,
32 | DisplayInfo = 1007,
33 | Caption = 1008,
34 | BorderInfo = 1009,
35 | BackgroundColor = 1010,
36 | PrintFlags = 1011,
37 | MultichannelHalftoneInfo = 1012,
38 | ColorHalftoneInfo = 1013,
39 | DuotoneHalftoneInfo = 1014,
40 | MultichannelTransferFunctions = 1015,
41 | ColorTransferFunctions = 1016,
42 | DuotoneTransferFunctions = 1017,
43 | DuotoneImageInfo = 1018,
44 | BlackWhiteRange = 1019,
45 | EpsOptions = 1021,
46 | QuickMaskInfo = 1022,
47 | LayerStateInfo = 1024,
48 | WorkingPathUnsaved = 1025,
49 | LayersGroupInfo = 1026,
50 | IptcNaa = 1028,
51 | RawFormatImageMode = 1029,
52 | JpegQuality = 1030,
53 | GridGuidesInfo = 1032,
54 | ThumbnailBgr = 1033,
55 | CopyrightInfo = 1034,
56 | Url = 1035,
57 | ThumbnailRgb = 1036,
58 | GlobalAngle = 1037,
59 | ColorSamplersObsolete = 1038,
60 | IccProfile = 1039,
61 | Watermark = 1040,
62 | IccUntagged = 1041,
63 | EffectsVisible = 1042,
64 | SpotHalftone = 1043,
65 | DocumentSpecific = 1044,
66 | UnicodeAlphaNames = 1045,
67 | IndexedColorTableCount = 1046,
68 | TransparentIndex = 1047,
69 | GlobalAltitude = 1049,
70 | Slices = 1050,
71 | WorkflowUrl = 1051,
72 | JumpToXpep = 1052,
73 | AlphaIdentifiers = 1053,
74 | UrlList = 1054,
75 | VersionInfo = 1057,
76 | ExifData1 = 1058,
77 | ExifData3 = 1059,
78 | XmpMetadata = 1060,
79 | CaptionDigest = 1061,
80 | PrintScale = 1062,
81 | PixelAspectRatio = 1064,
82 | LayerComps = 1065,
83 | AlternateDuotoneColors = 1066,
84 | AlternateSpotColors = 1067,
85 | LayerSelectionIDs = 1069,
86 | HdrToningInfo = 1070,
87 | PrintInfo = 1071,
88 | LayerGroupsEnabled = 1072,
89 | ColorSamplers = 1073,
90 | MeasurementScale = 1074,
91 | TimelineInfo = 1075,
92 | SheetDisclosure = 1076,
93 | FloatDisplayInfo = 1077,
94 | OnionSkins = 1078,
95 | CountInfo = 1080,
96 | PrintSettingsInfo = 1082,
97 | PrintStyle = 1083,
98 | MacNSPrintInfo = 1084,
99 | WinDevMode = 1085,
100 | AutoSaveFilePath = 1086,
101 | AutoSaveFormat = 1087,
102 | PathInfo = 2000, // 2000-2999: Path Information
103 | ClippingPathName = 2999,
104 | LightroomWorkflow = 8000,
105 | PrintFlagsInfo = 10000
106 | }
107 |
108 | ///
109 | /// Abstract class for Image Resources
110 | ///
111 | public abstract class ImageResource
112 | {
113 | private string signature;
114 | public string Signature
115 | {
116 | get { return signature; }
117 | set
118 | {
119 | if (value.Length != 4)
120 | throw new ArgumentException("Signature must have length of 4");
121 | signature = value;
122 | }
123 | }
124 |
125 | public string Name { get; set; }
126 |
127 | public abstract ResourceID ID { get; }
128 |
129 | protected ImageResource(string name)
130 | {
131 | Signature = "8BIM";
132 | Name = name;
133 | }
134 |
135 | ///
136 | /// Write out the image resource block: header and data.
137 | ///
138 | public void Save(PsdBinaryWriter writer)
139 | {
140 | writer.WriteAsciiChars(Signature);
141 | writer.Write((UInt16)ID);
142 | writer.WritePascalString(Name, 2);
143 |
144 | // Length is unpadded, but data is even-padded
145 | var startPosition = writer.BaseStream.Position;
146 | using (new PsdBlockLengthWriter(writer))
147 | {
148 | WriteData(writer);
149 | }
150 | writer.WritePadding(startPosition, 2);
151 | }
152 |
153 | ///
154 | /// Write the data for this image resource.
155 | ///
156 | protected abstract void WriteData(PsdBinaryWriter writer);
157 |
158 | public override string ToString()
159 | {
160 | return String.Format(CultureInfo.InvariantCulture, "{0} {1}", (ResourceID)ID, Name);
161 | }
162 | }
163 |
164 | ///
165 | /// Creates the appropriate subclass of ImageResource.
166 | ///
167 | public static class ImageResourceFactory
168 | {
169 | public static ImageResource CreateImageResource(PsdBinaryReader reader)
170 | {
171 | // Debug.Print("ImageResource started at {0}", reader.BaseStream.Position);
172 |
173 | var signature = reader.ReadAsciiChars(4);
174 | var resourceIdInt = reader.ReadUInt16();
175 | var name = reader.ReadPascalString(2);
176 | var dataLength = (int)reader.ReadUInt32();
177 |
178 | var dataPaddedLength = Util.RoundUp(dataLength, 2);
179 | var endPosition = reader.BaseStream.Position + dataPaddedLength;
180 |
181 | ImageResource resource = null;
182 | var resourceId = (ResourceID)resourceIdInt;
183 | switch (resourceId)
184 | {
185 | case ResourceID.ResolutionInfo:
186 | resource = new ResolutionInfo(reader, name);
187 | break;
188 | case ResourceID.ThumbnailRgb:
189 | case ResourceID.ThumbnailBgr:
190 | resource = new Thumbnail(reader, resourceId, name, dataLength);
191 | break;
192 | case ResourceID.AlphaChannelNames:
193 | resource = new AlphaChannelNames(reader, name, dataLength);
194 | break;
195 | case ResourceID.UnicodeAlphaNames:
196 | resource = new UnicodeAlphaNames(reader, name, dataLength);
197 | break;
198 | case ResourceID.VersionInfo:
199 | resource = new VersionInfo(reader, name);
200 | break;
201 | default:
202 | resource = new RawImageResource(reader, signature, resourceId, name, dataLength);
203 | break;
204 | }
205 |
206 | // Reposition the reader if we do not consume the full resource block.
207 | // This takes care of the even-padding, and also preserves forward-
208 | // compatibility in case a resource block is later extended with
209 | // additional properties.
210 | if (reader.BaseStream.Position < endPosition)
211 | reader.BaseStream.Position = endPosition;
212 |
213 | // However, overruns are definitely an error.
214 | if (reader.BaseStream.Position > endPosition)
215 | throw new PsdInvalidException("Corruption detected in resource.");
216 |
217 | return resource;
218 | }
219 | }
220 |
221 | public class ImageResources : List
222 | {
223 | public ImageResources() : base()
224 | {
225 | }
226 |
227 | public ImageResource Get(ResourceID id)
228 | {
229 | return Find(x => x.ID == id);
230 | }
231 |
232 | public void Set(ImageResource resource)
233 | {
234 | Predicate matchId = delegate(ImageResource res)
235 | {
236 | return res.ID == resource.ID;
237 | };
238 | var itemIdx = this.FindIndex(matchId);
239 | var lastItemIdx = this.FindLastIndex(matchId);
240 |
241 | if (itemIdx == -1)
242 | {
243 | Add(resource);
244 | }
245 | else if (itemIdx != lastItemIdx)
246 | {
247 | RemoveAll(matchId);
248 | Insert(itemIdx, resource);
249 | }
250 | else
251 | {
252 | this[itemIdx] = resource;
253 | }
254 | }
255 | }
256 |
257 | }
258 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Util.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // See LICENSE.txt for complete licensing and attribution information.
11 | //
12 | /////////////////////////////////////////////////////////////////////////////////
13 |
14 | using System;
15 | using System.Collections.Generic;
16 | using System.Diagnostics;
17 | using System.Linq;
18 | using System.Text;
19 | using UnityEngine;
20 |
21 | namespace PhotoshopFile
22 | {
23 | public static class Util
24 | {
25 | [DebuggerDisplay("Top = {Top}, Bottom = {Bottom}, Left = {Left}, Right = {Right}")]
26 | public struct RectanglePosition
27 | {
28 | public int Top { get; set; }
29 | public int Bottom { get; set; }
30 | public int Left { get; set; }
31 | public int Right { get; set; }
32 | }
33 |
34 | ///////////////////////////////////////////////////////////////////////////
35 |
36 | ///
37 | /// Fills a buffer with a byte value.
38 | ///
39 | unsafe static public void Fill(byte* ptr, byte* ptrEnd, byte value)
40 | {
41 | while (ptr < ptrEnd)
42 | {
43 | *ptr = value;
44 | ptr++;
45 | }
46 | }
47 |
48 | ///////////////////////////////////////////////////////////////////////////
49 |
50 | ///
51 | /// Reverses the endianness of a 2-byte word.
52 | ///
53 | unsafe static public void SwapBytes2(byte* ptr)
54 | {
55 | byte byte0 = *ptr;
56 | *ptr = *(ptr + 1);
57 | *(ptr + 1) = byte0;
58 | }
59 |
60 | ///////////////////////////////////////////////////////////////////////////
61 |
62 | ///
63 | /// Reverses the endianness of a 4-byte word.
64 | ///
65 | unsafe static public void SwapBytes4(byte* ptr)
66 | {
67 | byte byte0 = *ptr;
68 | byte byte1 = *(ptr + 1);
69 |
70 | *ptr = *(ptr + 3);
71 | *(ptr + 1) = *(ptr + 2);
72 | *(ptr + 2) = byte1;
73 | *(ptr + 3) = byte0;
74 | }
75 |
76 | ///
77 | /// Reverses the endianness of a word of arbitrary length.
78 | ///
79 | unsafe static public void SwapBytes(byte* ptr, int nLength)
80 | {
81 | for (long i = 0; i < nLength / 2; ++i)
82 | {
83 | byte t = *(ptr + i);
84 | *(ptr + i) = *(ptr + nLength - i - 1);
85 | *(ptr + nLength - i - 1) = t;
86 | }
87 | }
88 |
89 | ///////////////////////////////////////////////////////////////////////////
90 |
91 | ///
92 | /// Reverses the endianness of 2-byte words in a byte array.
93 | ///
94 | /// Byte array containing the sequence on which to swap endianness
95 | /// Byte index of the first word to swap
96 | /// Number of words to swap
97 | public static void SwapByteArray2(byte[] byteArray, int startIdx, int count)
98 | {
99 | int endIdx = startIdx + count * 2;
100 | if (byteArray.Length < endIdx)
101 | throw new IndexOutOfRangeException();
102 |
103 | unsafe
104 | {
105 | fixed (byte* arrayPtr = &byteArray[0])
106 | {
107 | byte* ptr = arrayPtr + startIdx;
108 | byte* endPtr = arrayPtr + endIdx;
109 | while (ptr < endPtr)
110 | {
111 | SwapBytes2(ptr);
112 | ptr += 2;
113 | }
114 | }
115 | }
116 | }
117 |
118 | ///
119 | /// Reverses the endianness of 4-byte words in a byte array.
120 | ///
121 | /// Byte array containing the sequence on which to swap endianness
122 | /// Byte index of the first word to swap
123 | /// Number of words to swap
124 | public static void SwapByteArray4(byte[] byteArray, int startIdx, int count)
125 | {
126 | int endIdx = startIdx + count * 4;
127 | if (byteArray.Length < endIdx)
128 | throw new IndexOutOfRangeException();
129 |
130 | unsafe
131 | {
132 | fixed (byte* arrayPtr = &byteArray[0])
133 | {
134 | byte* ptr = arrayPtr + startIdx;
135 | byte* endPtr = arrayPtr + endIdx;
136 | while (ptr < endPtr)
137 | {
138 | SwapBytes4(ptr);
139 | ptr += 4;
140 | }
141 | }
142 | }
143 | }
144 |
145 | ///////////////////////////////////////////////////////////////////////////
146 |
147 | public static int BytesPerRow(Rect rect, int depth)
148 | {
149 | switch (depth)
150 | {
151 | case 1:
152 | return ((int)rect.width + 7) / 8;
153 | default:
154 | return (int)rect.width * BytesFromBitDepth(depth);
155 | }
156 | }
157 |
158 | ///
159 | /// Round the integer to a multiple.
160 | ///
161 | public static int RoundUp(int value, int multiple)
162 | {
163 | if (value == 0)
164 | return 0;
165 |
166 | if (Math.Sign(value) != Math.Sign(multiple))
167 | throw new ArgumentException("value and multiple cannot have opposite signs.");
168 |
169 | var remainder = value % multiple;
170 | if (remainder > 0)
171 | {
172 | value += (multiple - remainder);
173 | }
174 | return value;
175 | }
176 |
177 | ///
178 | /// Get number of bytes required to pad to the specified multiple.
179 | ///
180 | public static int GetPadding(int length, int padMultiple)
181 | {
182 | if ((length < 0) || (padMultiple < 0))
183 | throw new ArgumentException();
184 |
185 | var remainder = length % padMultiple;
186 | if (remainder == 0)
187 | return 0;
188 |
189 | var padding = padMultiple - remainder;
190 | return padding;
191 | }
192 |
193 | public static int BytesFromBitDepth(int depth)
194 | {
195 | switch (depth)
196 | {
197 | case 1:
198 | case 8:
199 | return 1;
200 | case 16:
201 | return 2;
202 | case 32:
203 | return 4;
204 | default:
205 | throw new ArgumentException("Invalid bit depth.");
206 | }
207 | }
208 |
209 | public static short MinChannelCount(this PsdColorMode colorMode)
210 | {
211 | switch (colorMode)
212 | {
213 | case PsdColorMode.Bitmap:
214 | case PsdColorMode.Duotone:
215 | case PsdColorMode.Grayscale:
216 | case PsdColorMode.Indexed:
217 | case PsdColorMode.Multichannel:
218 | return 1;
219 | case PsdColorMode.Lab:
220 | case PsdColorMode.RGB:
221 | return 3;
222 | case PsdColorMode.CMYK:
223 | return 4;
224 | }
225 |
226 | throw new ArgumentException("Unknown color mode.");
227 | }
228 |
229 | ///
230 | /// Verify that the offset and count will remain within the bounds of the
231 | /// buffer.
232 | ///
233 | /// True if in bounds, false if out of bounds.
234 | public static bool CheckBufferBounds(byte[] data, int offset, int count)
235 | {
236 | if (offset < 0)
237 | return false;
238 | if (count < 0)
239 | return false;
240 | if (offset + count > data.Length)
241 | return false;
242 |
243 | return true;
244 | }
245 | }
246 |
247 | ///
248 | /// Fixed-point decimal, with 16-bit integer and 16-bit fraction.
249 | ///
250 | public class UFixed16_16
251 | {
252 | public UInt16 Integer { get; set; }
253 | public UInt16 Fraction { get; set; }
254 |
255 | public UFixed16_16(UInt16 integer, UInt16 fraction)
256 | {
257 | Integer = integer;
258 | Fraction = fraction;
259 | }
260 |
261 | ///
262 | /// Split the high and low words of a 32-bit unsigned integer into a
263 | /// fixed-point number.
264 | ///
265 | public UFixed16_16(UInt32 value)
266 | {
267 | Integer = (UInt16)(value >> 16);
268 | Fraction = (UInt16)(value & 0x0000ffff);
269 | }
270 |
271 | public UFixed16_16(double value)
272 | {
273 | if (value >= 65536.0) throw new OverflowException();
274 | if (value < 0) throw new OverflowException();
275 |
276 | Integer = (UInt16)value;
277 |
278 | // Round instead of truncate, because doubles may not represent the
279 | // fraction exactly.
280 | Fraction = (UInt16)((value - Integer) * 65536 + 0.5);
281 | }
282 |
283 | public static implicit operator double(UFixed16_16 value)
284 | {
285 | return (double)value.Integer + value.Fraction / 65536.0;
286 | }
287 |
288 | }
289 |
290 | }
291 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/Layer.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Collections.Generic;
19 | using System.Collections.Specialized;
20 | using System.Diagnostics;
21 | using System.Globalization;
22 | using System.IO;
23 | using System.Linq;
24 | using System.Threading;
25 | using UnityEngine;
26 |
27 | namespace PhotoshopFile
28 | {
29 |
30 | [DebuggerDisplay("Name = {Name}")]
31 | public class Layer
32 | {
33 | internal PsdFile PsdFile { get; private set; }
34 |
35 | ///
36 | /// The rectangle containing the contents of the layer.
37 | ///
38 | public Rect Rect { get; set; }
39 |
40 | ///
41 | /// Image channels.
42 | ///
43 | public ChannelList Channels { get; private set; }
44 |
45 | ///
46 | /// Returns alpha channel if it exists, otherwise null.
47 | ///
48 | public Channel AlphaChannel
49 | {
50 | get
51 | {
52 | if (Channels.ContainsId(-1))
53 | return Channels.GetId(-1);
54 | else
55 | return null;
56 | }
57 | }
58 |
59 | private string blendModeKey;
60 | ///
61 | /// Photoshop blend mode key for the layer
62 | ///
63 | public string BlendModeKey
64 | {
65 | get { return blendModeKey; }
66 | set
67 | {
68 | if (value.Length != 4) throw new ArgumentException("Key length must be 4");
69 | blendModeKey = value;
70 | }
71 | }
72 |
73 | ///
74 | /// 0 = transparent ... 255 = opaque
75 | ///
76 | public byte Opacity { get; set; }
77 |
78 | ///
79 | /// false = base, true = non-base
80 | ///
81 | public bool Clipping { get; set; }
82 |
83 | private static int protectTransBit = BitVector32.CreateMask();
84 | private static int visibleBit = BitVector32.CreateMask(protectTransBit);
85 | BitVector32 flags = new BitVector32();
86 |
87 | ///
88 | /// If true, the layer is visible.
89 | ///
90 | public bool Visible
91 | {
92 | get { return !flags[visibleBit]; }
93 | set { flags[visibleBit] = !value; }
94 | }
95 |
96 | ///
97 | /// Protect the transparency
98 | ///
99 | public bool ProtectTrans
100 | {
101 | get { return flags[protectTransBit]; }
102 | set { flags[protectTransBit] = value; }
103 | }
104 |
105 | ///
106 | /// The descriptive layer name
107 | ///
108 | public string Name { get; set; }
109 |
110 | public BlendingRanges BlendingRangesData { get; set; }
111 |
112 | public MaskInfo Masks { get; set; }
113 |
114 | public List AdditionalInfo { get; set; }
115 |
116 | ///////////////////////////////////////////////////////////////////////////
117 |
118 | public Layer(PsdFile psdFile)
119 | {
120 | PsdFile = psdFile;
121 | Rect = new Rect();
122 | Channels = new ChannelList();
123 | BlendModeKey = PsdBlendMode.Normal;
124 | AdditionalInfo = new List();
125 | }
126 |
127 | public Layer(PsdBinaryReader reader, PsdFile psdFile)
128 | : this(psdFile)
129 | {
130 | Rect = reader.ReadRectangle();
131 |
132 | //-----------------------------------------------------------------------
133 | // Read channel headers. Image data comes later, after the layer header.
134 |
135 | int numberOfChannels = reader.ReadUInt16();
136 | for (int channel = 0; channel < numberOfChannels; channel++)
137 | {
138 | var ch = new Channel(reader, this);
139 | Channels.Add(ch);
140 | }
141 |
142 | //-----------------------------------------------------------------------
143 | //
144 |
145 | var signature = reader.ReadAsciiChars(4);
146 | if (signature != "8BIM")
147 | throw (new PsdInvalidException("Invalid signature in layer header."));
148 |
149 | BlendModeKey = reader.ReadAsciiChars(4);
150 | Opacity = reader.ReadByte();
151 | Clipping = reader.ReadBoolean();
152 |
153 | var flagsByte = reader.ReadByte();
154 | flags = new BitVector32(flagsByte);
155 | reader.ReadByte(); //padding
156 |
157 | //-----------------------------------------------------------------------
158 |
159 | // This is the total size of the MaskData, the BlendingRangesData, the
160 | // Name and the AdjustmentLayerInfo.
161 | var extraDataSize = reader.ReadUInt32();
162 | var extraDataStartPosition = reader.BaseStream.Position;
163 |
164 | Masks = new MaskInfo(reader, this);
165 | BlendingRangesData = new BlendingRanges(reader, this);
166 | Name = reader.ReadPascalString(4);
167 |
168 | //-----------------------------------------------------------------------
169 | // Process Additional Layer Information
170 |
171 | long adjustmentLayerEndPos = extraDataStartPosition + extraDataSize;
172 | while (reader.BaseStream.Position < adjustmentLayerEndPos)
173 | {
174 | var layerInfo = LayerInfoFactory.Load(reader);
175 | AdditionalInfo.Add(layerInfo);
176 | }
177 |
178 | foreach (var adjustmentInfo in AdditionalInfo)
179 | {
180 | switch (adjustmentInfo.Key)
181 | {
182 | case "luni":
183 | Name = ((LayerUnicodeName)adjustmentInfo).Name;
184 | break;
185 | }
186 | }
187 |
188 | }
189 |
190 | ///////////////////////////////////////////////////////////////////////////
191 |
192 | ///
193 | /// Create ImageData for any missing channels.
194 | ///
195 | public void CreateMissingChannels()
196 | {
197 | var channelCount = this.PsdFile.ColorMode.MinChannelCount();
198 | for (short id = 0; id < channelCount; id++)
199 | {
200 | if (!this.Channels.ContainsId(id))
201 | {
202 | var size = (int)(this.Rect.height * this.Rect.width);
203 |
204 | var ch = new Channel(id, this);
205 | ch.ImageData = new byte[size];
206 | unsafe
207 | {
208 | fixed (byte* ptr = &ch.ImageData[0])
209 | {
210 | Util.Fill(ptr, ptr + size, (byte)255);
211 | }
212 | }
213 |
214 | this.Channels.Add(ch);
215 | }
216 | }
217 | }
218 |
219 | ///////////////////////////////////////////////////////////////////////////
220 |
221 | public void PrepareSave()
222 | {
223 | foreach (var ch in Channels)
224 | {
225 | ch.CompressImageData();
226 | }
227 |
228 | // Create or update the Unicode layer name to be consistent with the
229 | // ANSI layer name.
230 | var layerUnicodeNames = AdditionalInfo.Where(x => x is LayerUnicodeName);
231 | if (layerUnicodeNames.Count() > 1)
232 | throw new PsdInvalidException("Layer has more than one LayerUnicodeName.");
233 |
234 | var layerUnicodeName = (LayerUnicodeName) layerUnicodeNames.FirstOrDefault();
235 | if (layerUnicodeName == null)
236 | {
237 | layerUnicodeName = new LayerUnicodeName(Name);
238 | AdditionalInfo.Add(layerUnicodeName);
239 | }
240 | else if (layerUnicodeName.Name != Name)
241 | {
242 | layerUnicodeName.Name = Name;
243 | }
244 | }
245 |
246 | public void Save(PsdBinaryWriter writer)
247 | {
248 | writer.Write(Rect);
249 |
250 | //-----------------------------------------------------------------------
251 |
252 | writer.Write((short)Channels.Count);
253 | foreach (var ch in Channels)
254 | ch.Save(writer);
255 |
256 | //-----------------------------------------------------------------------
257 |
258 | writer.WriteAsciiChars("8BIM");
259 | writer.WriteAsciiChars(BlendModeKey);
260 | writer.Write(Opacity);
261 | writer.Write(Clipping);
262 |
263 | writer.Write((byte)flags.Data);
264 |
265 | //-----------------------------------------------------------------------
266 |
267 | writer.Write((byte)0);
268 |
269 | //-----------------------------------------------------------------------
270 |
271 | using (new PsdBlockLengthWriter(writer))
272 | {
273 | Masks.Save(writer);
274 | BlendingRangesData.Save(writer);
275 |
276 | var namePosition = writer.BaseStream.Position;
277 |
278 | // Legacy layer name is limited to 31 bytes. Unicode layer name
279 | // can be much longer.
280 | writer.WritePascalString(Name, 4, 31);
281 |
282 | foreach (LayerInfo info in AdditionalInfo)
283 | {
284 | info.Save(writer);
285 | }
286 | }
287 | }
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/PhotoShopFileType/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Layer Group: {0}
122 | Wraps the names of layer groups in the Layers palette. {0} is the layer group name.
123 |
124 |
125 | End Layer Group: {0}
126 | Wraps the names of layer groups in the Layers palette, at the end of the group. {0} is the layer group name.
127 |
128 |
129 | RLE compression
130 | RLE compression checkbox on the Save Configuration dialog.
131 |
132 |
133 | Ebenengruppe: {0}
134 |
135 |
136 | Ende Ebenengruppe: {0}
137 |
138 |
139 | RLE-Komprimierung
140 |
141 |
142 | Grupo de Capas: {0}
143 |
144 |
145 | Final del Grupo: {0}
146 |
147 |
148 | Compresión RLE
149 |
150 |
151 | Groupe de Calques: {0}
152 |
153 |
154 | Fin de Groupe: {0}
155 |
156 |
157 | Compression RLE
158 |
159 |
160 | Gruppo di Livelli: {0}
161 |
162 |
163 | Fin del Gruppo: {0}
164 |
165 |
166 | Compressione RLE
167 |
168 |
169 | レイヤーグループ: {0}
170 |
171 |
172 | レイヤーグループの終わり: {0}
173 |
174 |
175 | RLE 圧縮
176 |
177 |
178 | 레이어 그룹: {0}
179 |
180 |
181 | 레이어 그룹 끝: {0}
182 |
183 |
184 | RLE 압축
185 |
186 |
187 | Grupo de Camadas: {0}
188 |
189 |
190 | Final do Grupo: {0}
191 |
192 |
193 | Compactação RLE
194 |
195 |
196 | Группа слоев: {0}
197 |
198 |
199 | Конец Группа слоев: {0}
200 |
201 |
202 | RLE-сжатие
203 |
204 |
205 | 图层组:{0}
206 |
207 |
208 | 图层组结束:{0}
209 |
210 |
211 | RLE 压缩
212 |
213 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/Layers/Channel.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Collections.Generic;
19 | using System.Diagnostics;
20 | using System.Globalization;
21 | using System.IO;
22 | using System.IO.Compression;
23 | using System.Linq;
24 | using System.Text;
25 | using UnityEngine;
26 |
27 | namespace PhotoshopFile
28 | {
29 | public class ChannelList : List
30 | {
31 | ///
32 | /// Returns channels with nonnegative IDs as an array, so that accessing
33 | /// a channel by Id can be optimized into pointer arithmetic rather than
34 | /// being implemented as a List scan.
35 | ///
36 | ///
37 | /// This optimization is crucial for blitting lots of pixels back and
38 | /// forth between Photoshop's per-channel representation, and Paint.NET's
39 | /// per-pixel BGRA representation.
40 | ///
41 | public Channel[] ToIdArray()
42 | {
43 | var maxId = this.Max(x => x.ID);
44 | var idArray = new Channel[maxId + 1];
45 | foreach (var channel in this)
46 | {
47 | if (channel.ID >= 0)
48 | idArray[channel.ID] = channel;
49 | }
50 | return idArray;
51 | }
52 |
53 | public ChannelList()
54 | : base()
55 | {
56 | }
57 |
58 | public Channel GetId(int id)
59 | {
60 | return this.Single(x => x.ID == id);
61 | }
62 |
63 | public bool ContainsId(int id)
64 | {
65 | return this.Exists(x => x.ID == id);
66 | }
67 | }
68 |
69 | ///////////////////////////////////////////////////////////////////////////
70 |
71 | [DebuggerDisplay("ID = {ID}")]
72 | public class Channel
73 | {
74 | ///
75 | /// The layer to which this channel belongs
76 | ///
77 | public Layer Layer { get; private set; }
78 |
79 | ///
80 | /// Channel ID.
81 | ///
82 | /// - -1 = transparency mask
83 | /// - -2 = user-supplied layer mask, or vector mask
84 | /// - -3 = user-supplied layer mask, if channel -2 contains a vector mask
85 | /// -
86 | /// Nonnegative channel IDs give the actual image channels, in the
87 | /// order defined by the colormode. For example, 0, 1, 2 = R, G, B.
88 | ///
89 | ///
90 | ///
91 | public short ID { get; set; }
92 |
93 | public Rect Rect
94 | {
95 | get
96 | {
97 | switch (ID)
98 | {
99 | case -2:
100 | return Layer.Masks.LayerMask.Rect;
101 | case -3:
102 | return Layer.Masks.UserMask.Rect;
103 | default:
104 | return Layer.Rect;
105 | }
106 | }
107 | }
108 |
109 | ///
110 | /// Total length of the channel data, including compression headers.
111 | ///
112 | public int Length { get; set; }
113 |
114 | ///
115 | /// Raw image data for this color channel, in compressed on-disk format.
116 | ///
117 | ///
118 | /// If null, the ImageData will be automatically compressed during save.
119 | ///
120 | public byte[] ImageDataRaw { get; set; }
121 |
122 | ///
123 | /// Decompressed image data for this color channel.
124 | ///
125 | ///
126 | /// When making changes to the ImageData, set ImageDataRaw to null so that
127 | /// the correct data will be compressed during save.
128 | ///
129 | public byte[] ImageData { get; set; }
130 |
131 | ///
132 | /// Image compression method used.
133 | ///
134 | public ImageCompression ImageCompression { get; set; }
135 |
136 | ///
137 | /// RLE-compressed length of each row.
138 | ///
139 | public RleRowLengths RleRowLengths { get; set; }
140 |
141 | //////////////////////////////////////////////////////////////////
142 |
143 | internal Channel(short id, Layer layer)
144 | {
145 | ID = id;
146 | Layer = layer;
147 | }
148 |
149 | internal Channel(PsdBinaryReader reader, Layer layer)
150 | {
151 | ID = reader.ReadInt16();
152 | Length = reader.ReadInt32();
153 | Layer = layer;
154 | }
155 |
156 | internal void Save(PsdBinaryWriter writer)
157 | {
158 | writer.Write(ID);
159 | writer.Write(Length);
160 | }
161 |
162 | //////////////////////////////////////////////////////////////////
163 |
164 | internal void LoadPixelData(PsdBinaryReader reader)
165 | {
166 | var endPosition = reader.BaseStream.Position + this.Length;
167 | ImageCompression = (ImageCompression)reader.ReadInt16();
168 | var dataLength = this.Length - 2;
169 |
170 | switch (ImageCompression)
171 | {
172 | case ImageCompression.Raw:
173 | ImageDataRaw = reader.ReadBytes(dataLength);
174 | break;
175 | case ImageCompression.Rle:
176 | // RLE row lengths
177 | RleRowLengths = new RleRowLengths(reader, (int)Rect.height);
178 | var rleDataLength = (int)(endPosition - reader.BaseStream.Position);
179 |
180 | // The PSD specification states that rows are padded to even sizes.
181 | // However, Photoshop doesn't actually do this. RLE rows can have
182 | // odd lengths in the header, and there is no padding between rows.
183 | ImageDataRaw = reader.ReadBytes(rleDataLength);
184 | break;
185 | case ImageCompression.Zip:
186 | case ImageCompression.ZipPrediction:
187 | ImageDataRaw = reader.ReadBytes(dataLength);
188 | break;
189 | }
190 |
191 | }
192 |
193 | ///
194 | /// Decodes the raw image data from the compressed on-disk format into
195 | /// an uncompressed bitmap, in native byte order.
196 | ///
197 | public void DecodeImageData()
198 | {
199 | if (this.ImageCompression == ImageCompression.Raw)
200 | ImageData = ImageDataRaw;
201 | else
202 | DecompressImageData();
203 |
204 | // Rearrange the decompressed bytes into words, with native byte order.
205 | if (ImageCompression == ImageCompression.ZipPrediction)
206 | UnpredictImageData(Rect);
207 | else
208 | ReverseEndianness(ImageData, Rect);
209 | }
210 |
211 | private void DecompressImageData()
212 | {
213 | using (var stream = new MemoryStream(ImageDataRaw))
214 | {
215 | var bytesPerRow = Util.BytesPerRow(Rect, Layer.PsdFile.BitDepth);
216 | var bytesTotal = (int)Rect.height * bytesPerRow;
217 | ImageData = new byte[bytesTotal];
218 |
219 | switch (this.ImageCompression)
220 | {
221 | case ImageCompression.Rle:
222 | var rleReader = new RleReader(stream);
223 | for (int i = 0; i < Rect.height; i++)
224 | {
225 | int rowIndex = i * bytesPerRow;
226 | rleReader.Read(ImageData, rowIndex, bytesPerRow);
227 | }
228 | break;
229 |
230 | case ImageCompression.Zip:
231 | case ImageCompression.ZipPrediction:
232 |
233 | // .NET implements Deflate (RFC 1951) but not zlib (RFC 1950),
234 | // so we have to skip the first two bytes.
235 | stream.ReadByte();
236 | stream.ReadByte();
237 |
238 | var deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
239 | var bytesDecompressed = deflateStream.Read(ImageData, 0, bytesTotal);
240 | break;
241 |
242 | default:
243 | throw new PsdInvalidException("Unknown image compression method.");
244 | }
245 | }
246 | }
247 |
248 | private void ReverseEndianness(byte[] buffer, Rect rect)
249 | {
250 | var byteDepth = Util.BytesFromBitDepth(Layer.PsdFile.BitDepth);
251 | int pixelsTotal = (int)(rect.width * rect.height);
252 | if (pixelsTotal == 0)
253 | return;
254 |
255 | if (byteDepth == 2)
256 | {
257 | Util.SwapByteArray2(buffer, 0, pixelsTotal);
258 | }
259 | else if (byteDepth == 4)
260 | {
261 | Util.SwapByteArray4(buffer, 0, pixelsTotal);
262 | }
263 | else if (byteDepth > 1)
264 | {
265 | throw new NotImplementedException("Byte-swapping implemented only for 16-bit and 32-bit depths.");
266 | }
267 | }
268 |
269 | ///
270 | /// Unpredicts the raw decompressed image data into a little-endian
271 | /// scanline bitmap.
272 | ///
273 | unsafe private void UnpredictImageData(Rect rect)
274 | {
275 | if (Layer.PsdFile.BitDepth == 16)
276 | {
277 | // 16-bitdepth images are delta-encoded word-by-word. The deltas
278 | // are thus big-endian and must be reversed for further processing.
279 | ReverseEndianness(ImageData, rect);
280 |
281 | fixed (byte* ptrData = &ImageData[0])
282 | {
283 | // Delta-decode each row
284 | for (int iRow = 0; iRow < rect.height; iRow++)
285 | {
286 | UInt16* ptr = (UInt16*)(ptrData + iRow * (int)rect.width * 2);
287 | UInt16* ptrEnd = (UInt16*)(ptrData + (iRow + 1) * (int)rect.width * 2);
288 |
289 | // Start with column index 1 on each row
290 | ptr++;
291 | while (ptr < ptrEnd)
292 | {
293 | *ptr = (UInt16)(*ptr + *(ptr - 1));
294 | ptr++;
295 | }
296 | }
297 | }
298 | }
299 | else if (Layer.PsdFile.BitDepth == 32)
300 | {
301 | var reorderedData = new byte[ImageData.Length];
302 | fixed (byte* ptrData = &ImageData[0])
303 | {
304 | // Delta-decode each row
305 | for (int iRow = 0; iRow < rect.height; iRow++)
306 | {
307 | byte* ptr = ptrData + iRow * (int)rect.width * 4;
308 | byte* ptrEnd = ptrData + (iRow + 1) * (int)rect.width * 4;
309 |
310 | // Start with column index 1 on each row
311 | ptr++;
312 | while (ptr < ptrEnd)
313 | {
314 | *ptr = (byte)(*ptr + *(ptr - 1));
315 | ptr++;
316 | }
317 | }
318 |
319 | // Within each row, the individual bytes of the 32-bit words are
320 | // packed together, high-order bytes before low-order bytes.
321 | // We now unpack them into words and reverse to little-endian.
322 | int offset1 = (int)rect.width;
323 | int offset2 = 2 * offset1;
324 | int offset3 = 3 * offset1;
325 | fixed (byte* dstPtrData = &reorderedData[0])
326 | {
327 | for (int iRow = 0; iRow < rect.height; iRow++)
328 | {
329 | byte* dstPtr = dstPtrData + iRow * (int)rect.width * 4;
330 | byte* dstPtrEnd = dstPtrData + (iRow + 1) * (int)rect.width * 4;
331 |
332 | byte* srcPtr = ptrData + iRow * (int)rect.width * 4;
333 |
334 | // Reverse to little-endian as we do the unpacking.
335 | while (dstPtr < dstPtrEnd)
336 | {
337 | *(dstPtr++) = *(srcPtr + offset3);
338 | *(dstPtr++) = *(srcPtr + offset2);
339 | *(dstPtr++) = *(srcPtr + offset1);
340 | *(dstPtr++) = *srcPtr;
341 |
342 | srcPtr++;
343 | }
344 | }
345 | }
346 | }
347 |
348 | ImageData = reorderedData;
349 | }
350 | else
351 | {
352 | throw new PsdInvalidException("ZIP with prediction is only available for 16 and 32 bit depths.");
353 | }
354 | }
355 |
356 | ///
357 | /// Compresses the image data.
358 | ///
359 | public void CompressImageData()
360 | {
361 | // Do not recompress if compressed data is already present.
362 | if (ImageDataRaw != null)
363 | return;
364 |
365 | if (ImageData == null)
366 | return;
367 |
368 | if (ImageCompression == ImageCompression.Raw)
369 | {
370 | ImageDataRaw = ImageData;
371 | this.Length = 2 + ImageDataRaw.Length;
372 | }
373 | else if (ImageCompression == ImageCompression.Rle)
374 | {
375 | RleRowLengths = new RleRowLengths((int)Rect.height);
376 |
377 | using (var dataStream = new MemoryStream())
378 | {
379 | var rleWriter = new RleWriter(dataStream);
380 | var bytesPerRow = Util.BytesPerRow(Rect, Layer.PsdFile.BitDepth);
381 | for (int row = 0; row < Rect.height; row++)
382 | {
383 | int rowIndex = row * (int)Rect.width;
384 | RleRowLengths[row] = rleWriter.Write(
385 | ImageData, rowIndex, bytesPerRow);
386 | }
387 |
388 | // Save compressed data
389 | dataStream.Flush();
390 | ImageDataRaw = dataStream.ToArray();
391 | }
392 | Length = 2 + 2 * (int)Rect.height + ImageDataRaw.Length;
393 | }
394 | else
395 | {
396 | throw new NotImplementedException("Only raw and RLE compression have been implemented.");
397 | }
398 | }
399 |
400 | internal void SavePixelData(PsdBinaryWriter writer)
401 | {
402 | writer.Write((short)ImageCompression);
403 | if (ImageDataRaw == null)
404 | return;
405 |
406 | if (ImageCompression == PhotoshopFile.ImageCompression.Rle)
407 | RleRowLengths.Write(writer);
408 | writer.Write(ImageDataRaw);
409 | }
410 |
411 | }
412 | }
--------------------------------------------------------------------------------
/PhotoShopFileType/Editor/PSDEditorWindow.cs:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2013-2015 Banbury & Play-Em
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | */
24 |
25 | using PhotoshopFile;
26 | using System;
27 | using System.Collections.Generic;
28 | using System.IO;
29 | using System.Linq;
30 | using System.Text;
31 | using UnityEditor;
32 | using UnityEngine;
33 |
34 | public class PSDEditorWindow : EditorWindow {
35 | private Texture2D image;
36 | private Vector2 scrollPos;
37 | private PsdFile psd;
38 | private int atlassize = 4096;
39 | private float pixelsToUnitSize = 100.0f;
40 | private bool importIntoSelected = false;
41 | private string fileName;
42 | private bool useSizeDelta;
43 |
44 | private Transform selectedTransform;
45 |
46 | [MenuItem("Sprites/PSD Import")]
47 | public static void ShowWindow() {
48 | var wnd = GetWindow();
49 | wnd.title = "PSD Import";
50 | wnd.Show();
51 | }
52 |
53 | public void OnGUI() {
54 | EditorGUI.BeginChangeCheck();
55 | image = (Texture2D)EditorGUILayout.ObjectField("PSD File", image, typeof(Texture2D), true);
56 | bool changed = EditorGUI.EndChangeCheck();
57 |
58 | if (image != null) {
59 | if (changed) {
60 | string path = AssetDatabase.GetAssetPath(image);
61 |
62 | if (path.ToUpper().EndsWith(".PSD")) {
63 | psd = new PsdFile(path, Encoding.Default);
64 | fileName = Path.GetFileNameWithoutExtension(path);
65 | }
66 | else {
67 | psd = null;
68 | }
69 | }
70 | if (psd != null) {
71 | scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
72 |
73 | foreach (Layer layer in psd.Layers) {
74 | if (layer.Name != "" && layer.Name != "") {
75 | layer.Visible = EditorGUILayout.ToggleLeft(layer.Name, layer.Visible);
76 | }
77 | }
78 |
79 | EditorGUILayout.EndScrollView();
80 |
81 | if (GUILayout.Button("Export visible layers")) {
82 | ExportLayers();
83 | }
84 |
85 | atlassize = EditorGUILayout.IntField("Max. atlas size", atlassize);
86 |
87 | if (!((atlassize != 0) && ((atlassize & (atlassize - 1)) == 0))) {
88 | EditorGUILayout.HelpBox("Atlas size should be a power of 2", MessageType.Warning);
89 | }
90 |
91 | pixelsToUnitSize = EditorGUILayout.FloatField("Pixels To Unit Size", pixelsToUnitSize);
92 |
93 | if (pixelsToUnitSize <= 0) {
94 | EditorGUILayout.HelpBox("Pixels To Unit Size should be greater than 0.", MessageType.Warning);
95 | }
96 | importIntoSelected = EditorGUILayout.Toggle("Import into selected object", importIntoSelected);
97 | useSizeDelta = EditorGUILayout.Toggle("Use Size Delta", useSizeDelta);
98 | if (GUILayout.Button("Create atlas")) {
99 | CreateAtlas();
100 | }
101 | if (GUILayout.Button("Create sprites")) {
102 | CreateSprites();
103 | }
104 | if (GUILayout.Button("Create images")) {
105 | CreateImages();
106 | }
107 | }
108 | else {
109 | EditorGUILayout.HelpBox("This texture is not a PSD file.", MessageType.Error);
110 | }
111 | }
112 | }
113 |
114 | private Texture2D CreateTexture(Layer layer) {
115 | if ((int)layer.Rect.width == 0 || (int)layer.Rect.height == 0)
116 | return null;
117 |
118 | Texture2D tex = new Texture2D((int)layer.Rect.width, (int)layer.Rect.height, TextureFormat.RGBA32, true);
119 | Color32[] pixels = new Color32[tex.width * tex.height];
120 |
121 | Channel red = (from l in layer.Channels where l.ID == 0 select l).First();
122 | Channel green = (from l in layer.Channels where l.ID == 1 select l).First();
123 | Channel blue = (from l in layer.Channels where l.ID == 2 select l).First();
124 | Channel alpha = layer.AlphaChannel;
125 |
126 | for (int i = 0; i < pixels.Length; i++) {
127 | byte r = red.ImageData[i];
128 | byte g = green.ImageData[i];
129 | byte b = blue.ImageData[i];
130 | byte a = 255;
131 |
132 | if (alpha != null)
133 | a = alpha.ImageData[i];
134 |
135 | int mod = i % tex.width;
136 | int n = ((tex.width - mod - 1) + i) - mod;
137 | pixels[pixels.Length - n - 1] = new Color32(r, g, b, a);
138 | }
139 |
140 | tex.SetPixels32(pixels);
141 | tex.Apply();
142 | return tex;
143 | }
144 |
145 | private void ExportLayers() {
146 | foreach (Layer layer in psd.Layers) {
147 | if (layer.Visible) {
148 | Texture2D tex = CreateTexture(layer);
149 | if (tex == null) continue;
150 | SaveAsset(tex, "_" + layer.Name);
151 | DestroyImmediate(tex);
152 | }
153 | }
154 | }
155 |
156 | private void CreateAtlas() {
157 | // Texture2D[] textures = (from layer in psd.Layers where layer.Visible select CreateTexture(layer) into tex where tex != null select tex).ToArray();
158 |
159 | List textures = new List();
160 |
161 | // Track the spriteRenderers created via a List
162 | List spriteRenderers = new List();
163 |
164 | int zOrder = 0;
165 | GameObject root = new GameObject(fileName);
166 | foreach (var layer in psd.Layers) {
167 | if (layer.Visible && layer.Rect.width > 0 && layer.Rect.height > 0) {
168 | Texture2D tex = CreateTexture(layer);
169 | // Add the texture to the Texture Array
170 | textures.Add(tex);
171 |
172 | GameObject go = new GameObject(layer.Name);
173 | SpriteRenderer sr = go.AddComponent();
174 | go.transform.position = new Vector3((layer.Rect.width / 2 + layer.Rect.x) / pixelsToUnitSize, (-layer.Rect.height / 2 - layer.Rect.y) / pixelsToUnitSize, 0);
175 | // Add the sprite renderer to the SpriteRenderer Array
176 | spriteRenderers.Add(sr);
177 | sr.sortingOrder = zOrder++;
178 | go.transform.parent = root.transform;
179 | }
180 | }
181 |
182 | // The output of PackTextures returns a Rect array from which we can create our sprites
183 | Rect[] rects;
184 | Texture2D atlas = new Texture2D(atlassize, atlassize);
185 | Texture2D[] textureArray = textures.ToArray();
186 | rects = atlas.PackTextures(textureArray, 2, atlassize);
187 | List Sprites = new List();
188 |
189 | // For each rect in the Rect Array create the sprite and assign to the SpriteMetaData
190 | for (int i = 0; i < rects.Length; i++) {
191 | // add the name and rectangle to the dictionary
192 | SpriteMetaData smd = new SpriteMetaData();
193 | smd.name = spriteRenderers[i].name;
194 | smd.rect = new Rect(rects[i].xMin * atlas.width, rects[i].yMin * atlas.height, rects[i].width * atlas.width, rects[i].height * atlas.height);
195 | smd.pivot = new Vector2(0.5f, 0.5f); // Center is default otherwise layers will be misaligned
196 | smd.alignment = (int)SpriteAlignment.Center;
197 | Sprites.Add(smd);
198 | }
199 |
200 | // Need to load the image first
201 | string assetPath = AssetDatabase.GetAssetPath(image);
202 | string path = Path.Combine(Path.GetDirectoryName(assetPath),
203 | Path.GetFileNameWithoutExtension(assetPath) + "_atlas" + ".png");
204 |
205 | byte[] buf = atlas.EncodeToPNG();
206 | File.WriteAllBytes(path, buf);
207 | AssetDatabase.Refresh();
208 |
209 | // Get our texture that we loaded
210 | atlas = (Texture2D)AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D));
211 | TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
212 | // Make sure the size is the same as our atlas then create the spritesheet
213 | textureImporter.maxTextureSize = atlassize;
214 | textureImporter.spritesheet = Sprites.ToArray();
215 | textureImporter.textureType = TextureImporterType.Sprite;
216 | textureImporter.spriteImportMode = SpriteImportMode.Multiple;
217 | textureImporter.spritePivot = new Vector2(0.5f, 0.5f);
218 | textureImporter.spritePixelsPerUnit = pixelsToUnitSize;
219 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
220 |
221 | // For each rect in the Rect Array create the sprite and assign to the SpriteRenderer
222 | for (int j = 0; j < textureImporter.spritesheet.Length; j++) {
223 | // Debug.Log(textureImporter.spritesheet[j].rect);
224 | Sprite spr = Sprite.Create(atlas, textureImporter.spritesheet[j].rect, textureImporter.spritesheet[j].pivot, pixelsToUnitSize); // The 100.0f is for the pixels to unit, maybe make that a public variable for the user to change before hand?
225 |
226 | // Add the sprite to the sprite renderer
227 | spriteRenderers[j].sprite = spr;
228 | }
229 |
230 | foreach (Texture2D tex in textureArray) {
231 | DestroyImmediate(tex);
232 | }
233 | }
234 |
235 | private void CreateSprites() {
236 | if (importIntoSelected) {
237 | selectedTransform = Selection.activeTransform;
238 | }
239 | int zOrder = 0;
240 | GameObject root = new GameObject(fileName);
241 | if (importIntoSelected && selectedTransform != null) {
242 | root.transform.parent = selectedTransform;
243 | }
244 | foreach (var layer in psd.Layers) {
245 | if (layer.Visible && layer.Rect.width > 0 && layer.Rect.height > 0) {
246 | Texture2D tex = CreateTexture(layer);
247 | Sprite spr = SaveAsset(tex, "_" + layer.Name);
248 | DestroyImmediate(tex);
249 |
250 | GameObject go = new GameObject(layer.Name);
251 | SpriteRenderer sr = go.AddComponent();
252 | sr.sprite = spr;
253 | sr.sortingOrder = zOrder++;
254 | go.transform.position = new Vector3((layer.Rect.width / 2 + layer.Rect.x) / pixelsToUnitSize, (-layer.Rect.height / 2 - layer.Rect.y) / pixelsToUnitSize, 0);
255 | go.transform.parent = root.transform;
256 | }
257 | }
258 | }
259 | private void CreateImages() {
260 | if (importIntoSelected) {
261 | selectedTransform = Selection.activeTransform;
262 | }
263 | int zOrder = 0;
264 | GameObject root = new GameObject(fileName);
265 | root.transform.localPosition = new Vector3(0, 0, 0);
266 | var rtransf = root.AddComponent();
267 | if (importIntoSelected && selectedTransform != null)
268 | root.transform.parent = selectedTransform;
269 | rtransf.anchorMin = new Vector2(0f, 0f);
270 | rtransf.anchorMax = new Vector2(1f, 1f);
271 | rtransf.pivot = new Vector2(0.5f, 0.5f);
272 | rtransf.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
273 | rtransf.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0);
274 | rtransf.sizeDelta = Vector2.zero;
275 | rtransf.localPosition = Vector3.zero;
276 |
277 | foreach (var layer in psd.Layers) {
278 | if (layer.Visible && layer.Rect.width > 0 && layer.Rect.height > 0) {
279 | var targetOrder = zOrder++;
280 | Texture2D tex = CreateTexture(layer);
281 | Sprite spr = SaveAsset(tex, "_" + layer.Name);
282 | DestroyImmediate(tex);
283 |
284 | GameObject go = new GameObject(layer.Name);
285 | go.transform.parent = root.transform;
286 | go.transform.SetSiblingIndex(targetOrder);
287 | UnityEngine.UI.Image image = go.AddComponent();
288 | image.sprite = spr;
289 | image.rectTransform.localPosition = new Vector3((layer.Rect.x), (layer.Rect.y * -1) - layer.Rect.height, targetOrder * 5);
290 | image.rectTransform.anchorMax = new Vector2(0, 1);
291 | image.rectTransform.anchorMin = new Vector2(0, 1);
292 | image.rectTransform.pivot = new Vector2(0f, 0f);
293 | image.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, layer.Rect.width);
294 | image.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, layer.Rect.height);
295 | }
296 | }
297 | }
298 |
299 | private Sprite SaveAsset(Texture2D tex, string suffix) {
300 | string assetPath = AssetDatabase.GetAssetPath(image);
301 | string path = Path.Combine(Path.GetDirectoryName(assetPath),
302 | Path.GetFileNameWithoutExtension(assetPath) + suffix + ".png");
303 |
304 | byte[] buf = tex.EncodeToPNG();
305 | File.WriteAllBytes(path, buf);
306 | AssetDatabase.Refresh();
307 | // Load the texture so we can change the type
308 | AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D));
309 | TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
310 | textureImporter.textureType = TextureImporterType.Sprite;
311 | textureImporter.spriteImportMode = SpriteImportMode.Single;
312 | textureImporter.spritePivot = new Vector2(0.5f, 0.5f);
313 | textureImporter.spritePixelsPerUnit = pixelsToUnitSize;
314 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
315 |
316 | return (Sprite)AssetDatabase.LoadAssetAtPath(path, typeof(Sprite));
317 | }
318 |
319 | }
320 |
321 |
--------------------------------------------------------------------------------
/PhotoShopFileType/PsdFile/PsdFile.cs:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Photoshop PSD FileType Plugin for Paint.NET
4 | // http://psdplugin.codeplex.com/
5 | //
6 | // This software is provided under the MIT License:
7 | // Copyright (c) 2006-2007 Frank Blumenberg
8 | // Copyright (c) 2010-2013 Tao Yue
9 | //
10 | // Portions of this file are provided under the BSD 3-clause License:
11 | // Copyright (c) 2006, Jonas Beckeman
12 | //
13 | // See LICENSE.txt for complete licensing and attribution information.
14 | //
15 | /////////////////////////////////////////////////////////////////////////////////
16 |
17 | using System;
18 | using System.Collections.Generic;
19 | using System.Diagnostics;
20 | using System.Globalization;
21 | using System.IO;
22 | using System.Linq;
23 | using System.Text;
24 | using System.Threading;
25 | using UnityEngine;
26 |
27 |
28 | namespace PhotoshopFile {
29 | public enum PsdColorMode {
30 | Bitmap = 0,
31 | Grayscale = 1,
32 | Indexed = 2,
33 | RGB = 3,
34 | CMYK = 4,
35 | Multichannel = 7,
36 | Duotone = 8,
37 | Lab = 9
38 | };
39 |
40 |
41 | public class PsdFile {
42 | ///
43 | /// Represents the composite image.
44 | ///
45 | public Layer BaseLayer { get; set; }
46 |
47 | public ImageCompression ImageCompression { get; set; }
48 |
49 | ///////////////////////////////////////////////////////////////////////////
50 |
51 | public PsdFile() {
52 | Version = 1;
53 | BaseLayer = new Layer(this);
54 |
55 | ImageResources = new ImageResources();
56 | Layers = new List();
57 | AdditionalInfo = new List();
58 | }
59 |
60 | public PsdFile(string filename, Encoding encoding)
61 | : this() {
62 | using (var stream = new FileStream(filename, FileMode.Open)) {
63 | Load(stream, encoding);
64 | }
65 | }
66 |
67 | public PsdFile(Stream stream, Encoding encoding)
68 | : this() {
69 | Load(stream, encoding);
70 | }
71 |
72 | ///////////////////////////////////////////////////////////////////////////
73 |
74 | private void Load(Stream stream, Encoding encoding) {
75 | var reader = new PsdBinaryReader(stream, encoding);
76 |
77 | LoadHeader(reader);
78 | LoadColorModeData(reader);
79 | LoadImageResources(reader);
80 | LoadLayerAndMaskInfo(reader);
81 |
82 | LoadImage(reader);
83 | DecompressImages();
84 | }
85 |
86 | public void Save(string fileName, Encoding encoding) {
87 | using (var stream = new FileStream(fileName, FileMode.Create)) {
88 | Save(stream, encoding);
89 | }
90 | }
91 |
92 | public void Save(Stream stream, Encoding encoding) {
93 | if (BitDepth != 8)
94 | throw new NotImplementedException("Only 8-bit color has been implemented for saving.");
95 |
96 | var writer = new PsdBinaryWriter(stream, encoding);
97 | writer.AutoFlush = true;
98 |
99 | PrepareSave();
100 |
101 | SaveHeader(writer);
102 | SaveColorModeData(writer);
103 | SaveImageResources(writer);
104 | SaveLayerAndMaskInfo(writer);
105 | SaveImage(writer);
106 | }
107 |
108 | ///////////////////////////////////////////////////////////////////////////
109 |
110 | #region Header
111 |
112 | ///
113 | /// Always equal to 1.
114 | ///
115 | public Int16 Version { get; private set; }
116 |
117 | private Int16 channelCount;
118 | ///
119 | /// The number of channels in the image, including any alpha channels.
120 | ///
121 | public Int16 ChannelCount {
122 | get { return channelCount; }
123 | set {
124 | if (value < 1 || value > 56)
125 | throw new ArgumentException("Number of channels must be from 1 to 56.");
126 | channelCount = value;
127 | }
128 | }
129 |
130 | ///
131 | /// The height of the image in pixels.
132 | ///
133 | public int RowCount {
134 | get { return (int)this.BaseLayer.Rect.height; }
135 | set {
136 | if (value < 0 || value > 30000)
137 | throw new ArgumentException("Number of rows must be from 1 to 30000.");
138 | BaseLayer.Rect = new Rect(0, 0, BaseLayer.Rect.width, value);
139 | }
140 | }
141 |
142 |
143 | ///
144 | /// The width of the image in pixels.
145 | ///
146 | public int ColumnCount {
147 | get { return (int)this.BaseLayer.Rect.width; }
148 | set {
149 | if (value < 0 || value > 30000)
150 | throw new ArgumentException("Number of columns must be from 1 to 30000.");
151 | this.BaseLayer.Rect = new Rect(0, 0, value, this.BaseLayer.Rect.height);
152 | }
153 | }
154 |
155 | private int bitDepth;
156 | ///
157 | /// The number of bits per channel. Supported values are 1, 8, 16, and 32.
158 | ///
159 | public int BitDepth {
160 | get { return bitDepth; }
161 | set {
162 | switch (value) {
163 | case 1:
164 | case 8:
165 | case 16:
166 | case 32:
167 | bitDepth = value;
168 | break;
169 | default:
170 | throw new NotImplementedException("Invalid bit depth.");
171 | }
172 | }
173 | }
174 |
175 | ///
176 | /// The color mode of the file.
177 | ///
178 | public PsdColorMode ColorMode { get; set; }
179 |
180 | ///////////////////////////////////////////////////////////////////////////
181 |
182 | private void LoadHeader(PsdBinaryReader reader) {
183 | var signature = reader.ReadAsciiChars(4);
184 | if (signature != "8BPS")
185 | throw new PsdInvalidException("The given stream is not a valid PSD file");
186 |
187 | Version = reader.ReadInt16();
188 | if (Version != 1)
189 | throw new PsdInvalidException("The PSD file has an unknown version");
190 |
191 | //6 bytes reserved
192 | reader.BaseStream.Position += 6;
193 |
194 | this.ChannelCount = reader.ReadInt16();
195 | this.RowCount = reader.ReadInt32();
196 | this.ColumnCount = reader.ReadInt32();
197 | BitDepth = reader.ReadInt16();
198 | ColorMode = (PsdColorMode)reader.ReadInt16();
199 | }
200 |
201 | ///////////////////////////////////////////////////////////////////////////
202 |
203 | private void SaveHeader(PsdBinaryWriter writer) {
204 | string signature = "8BPS";
205 | writer.WriteAsciiChars(signature);
206 | writer.Write(Version);
207 | writer.Write(new byte[] { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, });
208 | writer.Write(ChannelCount);
209 | writer.Write(RowCount);
210 | writer.Write(ColumnCount);
211 | writer.Write((Int16)BitDepth);
212 | writer.Write((Int16)ColorMode);
213 | }
214 |
215 | #endregion
216 |
217 | ///////////////////////////////////////////////////////////////////////////
218 |
219 | #region ColorModeData
220 |
221 | ///
222 | /// If ColorMode is ColorModes.Indexed, the following 768 bytes will contain
223 | /// a 256-color palette. If the ColorMode is ColorModes.Duotone, the data
224 | /// following presumably consists of screen parameters and other related information.
225 | /// Unfortunately, it is intentionally not documented by Adobe, and non-Photoshop
226 | /// readers are advised to treat duotone images as gray-scale images.
227 | ///
228 | public byte[] ColorModeData = new byte[0];
229 |
230 | private void LoadColorModeData(PsdBinaryReader reader) {
231 | var paletteLength = reader.ReadUInt32();
232 | if (paletteLength > 0) {
233 | ColorModeData = reader.ReadBytes((int)paletteLength);
234 | }
235 | }
236 |
237 | private void SaveColorModeData(PsdBinaryWriter writer) {
238 | writer.Write((UInt32)ColorModeData.Length);
239 | writer.Write(ColorModeData);
240 | }
241 |
242 | #endregion
243 |
244 | ///////////////////////////////////////////////////////////////////////////
245 |
246 | #region ImageResources
247 |
248 | ///
249 | /// The Image resource blocks for the file
250 | ///
251 | public ImageResources ImageResources { get; set; }
252 |
253 | public ResolutionInfo Resolution {
254 | get {
255 | return (ResolutionInfo)ImageResources.Get(ResourceID.ResolutionInfo);
256 | }
257 |
258 | set {
259 | ImageResources.Set(value);
260 | }
261 | }
262 |
263 |
264 | ///////////////////////////////////////////////////////////////////////////
265 |
266 | private void LoadImageResources(PsdBinaryReader reader) {
267 | var imageResourcesLength = reader.ReadUInt32();
268 | if (imageResourcesLength <= 0)
269 | return;
270 |
271 | var startPosition = reader.BaseStream.Position;
272 | var endPosition = startPosition + imageResourcesLength;
273 | while (reader.BaseStream.Position < endPosition) {
274 | var imageResource = ImageResourceFactory.CreateImageResource(reader);
275 | ImageResources.Add(imageResource);
276 | }
277 |
278 | //-----------------------------------------------------------------------
279 | // make sure we are not on a wrong offset, so set the stream position
280 | // manually
281 | reader.BaseStream.Position = startPosition + imageResourcesLength;
282 | }
283 |
284 | ///////////////////////////////////////////////////////////////////////////
285 |
286 | private void SaveImageResources(PsdBinaryWriter writer) {
287 | using (new PsdBlockLengthWriter(writer)) {
288 | foreach (var imgRes in ImageResources)
289 | imgRes.Save(writer);
290 | }
291 | }
292 |
293 | #endregion
294 |
295 | ///////////////////////////////////////////////////////////////////////////
296 |
297 | #region LayerAndMaskInfo
298 |
299 | public List Layers { get; private set; }
300 |
301 | public List AdditionalInfo { get; private set; }
302 |
303 | public bool AbsoluteAlpha { get; set; }
304 |
305 | ///////////////////////////////////////////////////////////////////////////
306 |
307 | private void LoadLayerAndMaskInfo(PsdBinaryReader reader) {
308 | var layersAndMaskLength = reader.ReadUInt32();
309 | if (layersAndMaskLength <= 0)
310 | return;
311 |
312 | var startPosition = reader.BaseStream.Position;
313 | var endPosition = startPosition + layersAndMaskLength;
314 |
315 | LoadLayers(reader, true);
316 | LoadGlobalLayerMask(reader);
317 |
318 | //-----------------------------------------------------------------------
319 | // Load Additional Layer Information
320 |
321 | while (reader.BaseStream.Position < endPosition) {
322 | var info = LayerInfoFactory.Load(reader);
323 | AdditionalInfo.Add(info);
324 |
325 | if (info is RawLayerInfo) {
326 | var layerInfo = (RawLayerInfo)info;
327 | switch (info.Key) {
328 | case "Layr":
329 | case "Lr16":
330 | case "Lr32":
331 | using (var memoryStream = new MemoryStream(layerInfo.Data))
332 | using (var memoryReader = new PsdBinaryReader(memoryStream, reader)) {
333 | LoadLayers(memoryReader, false);
334 | }
335 | break;
336 |
337 | case "LMsk":
338 | GlobalLayerMaskData = layerInfo.Data;
339 | break;
340 | }
341 | }
342 | }
343 |
344 | //-----------------------------------------------------------------------
345 | // make sure we are not on a wrong offset, so set the stream position
346 | // manually
347 | reader.BaseStream.Position = startPosition + layersAndMaskLength;
348 | }
349 |
350 | ///////////////////////////////////////////////////////////////////////////
351 |
352 | private void SaveLayerAndMaskInfo(PsdBinaryWriter writer) {
353 | using (new PsdBlockLengthWriter(writer)) {
354 | var startPosition = writer.BaseStream.Position;
355 |
356 | SaveLayers(writer);
357 | SaveGlobalLayerMask(writer);
358 |
359 | foreach (var info in AdditionalInfo)
360 | info.Save(writer);
361 |
362 | writer.WritePadding(startPosition, 2);
363 | }
364 | }
365 |
366 | ///////////////////////////////////////////////////////////////////////////
367 |
368 | ///
369 | /// Load Layers Info section, including image data.
370 | ///
371 | /// PSD reader.
372 | /// Whether the Layers Info section has a length header.
373 | private void LoadLayers(PsdBinaryReader reader, bool hasHeader) {
374 | UInt32 sectionLength = 0;
375 | if (hasHeader) {
376 | sectionLength = reader.ReadUInt32();
377 | if (sectionLength <= 0)
378 | return;
379 | }
380 |
381 | var startPosition = reader.BaseStream.Position;
382 | var numLayers = reader.ReadInt16();
383 |
384 | // If numLayers < 0, then number of layers is absolute value,
385 | // and the first alpha channel contains the transparency data for
386 | // the merged result.
387 | if (numLayers < 0) {
388 | AbsoluteAlpha = true;
389 | numLayers = Math.Abs(numLayers);
390 | }
391 | if (numLayers == 0)
392 | return;
393 |
394 | for (int i = 0; i < numLayers; i++) {
395 | var layer = new Layer(reader, this);
396 | Layers.Add(layer);
397 | }
398 |
399 | //-----------------------------------------------------------------------
400 |
401 | // Load image data for all channels.
402 | foreach (var layer in Layers) {
403 | foreach (var channel in layer.Channels) {
404 | channel.LoadPixelData(reader);
405 | }
406 | }
407 |
408 | // Length is set to 0 when called on higher bitdepth layers.
409 | if (sectionLength > 0) {
410 | // Layers Info section is documented to be even-padded, but Photoshop
411 | // actually pads to 4 bytes.
412 | var endPosition = startPosition + sectionLength;
413 | var positionOffset = reader.BaseStream.Position - endPosition;
414 |
415 | if (reader.BaseStream.Position < endPosition)
416 | reader.BaseStream.Position = endPosition;
417 | }
418 | }
419 |
420 | ///////////////////////////////////////////////////////////////////////////
421 |
422 | ///
423 | /// Decompress the document image data and all the layers' image data, in parallel.
424 | ///
425 | private void DecompressImages() {
426 | var imageLayers = Layers.Concat(new List() { this.BaseLayer });
427 | foreach (var layer in imageLayers) {
428 | foreach (var channel in layer.Channels) {
429 | var dcc = new DecompressChannelContext(channel);
430 | var waitCallback = new WaitCallback(dcc.DecompressChannel);
431 | ThreadPool.QueueUserWorkItem(waitCallback);
432 | }
433 | }
434 |
435 | foreach (var layer in Layers) {
436 | foreach (var channel in layer.Channels) {
437 | if (channel.ID == -2)
438 | layer.Masks.LayerMask.ImageData = channel.ImageData;
439 | else if (channel.ID == -3)
440 | layer.Masks.UserMask.ImageData = channel.ImageData;
441 | }
442 | }
443 | }
444 |
445 | ///
446 | /// Check the validity of the PSD file and generate necessary data.
447 | ///
448 | public void PrepareSave() {
449 | var imageLayers = Layers.Concat(new List() { this.BaseLayer }).ToList();
450 |
451 | foreach (var layer in imageLayers) {
452 | layer.PrepareSave();
453 | }
454 |
455 | SetVersionInfo();
456 | VerifyLayerSections();
457 | }
458 |
459 | ///
460 | /// Verify validity of layer sections. Each start marker should have a
461 | /// matching end marker.
462 | ///
463 | internal void VerifyLayerSections() {
464 | int depth = 0;
465 | foreach (var layer in Enumerable.Reverse(Layers)) {
466 | var layerSectionInfo = layer.AdditionalInfo.SingleOrDefault(
467 | x => x is LayerSectionInfo);
468 | if (layerSectionInfo == null)
469 | continue;
470 |
471 | var sectionInfo = (LayerSectionInfo)layerSectionInfo;
472 | switch (sectionInfo.SectionType) {
473 | case LayerSectionType.OpenFolder:
474 | case LayerSectionType.ClosedFolder:
475 | depth++;
476 | break;
477 |
478 | case LayerSectionType.SectionDivider:
479 | depth--;
480 | if (depth < 0)
481 | throw new PsdInvalidException("Layer section ended without matching start marker.");
482 | break;
483 |
484 | default:
485 | throw new PsdInvalidException("Unrecognized layer section type.");
486 | }
487 | }
488 |
489 | if (depth != 0)
490 | throw new PsdInvalidException("Layer section not closed by end marker.");
491 | }
492 |
493 | ///
494 | /// Set the VersionInfo resource on the file.
495 | ///
496 | public void SetVersionInfo() {
497 | var versionInfo = (VersionInfo)ImageResources.Get(ResourceID.VersionInfo);
498 | if (versionInfo == null) {
499 | versionInfo = new VersionInfo();
500 | ImageResources.Set(versionInfo);
501 |
502 | // Get the version string. We don't use the fourth part (revision).
503 | var assembly = System.Reflection.Assembly.GetExecutingAssembly();
504 | var version = assembly.GetName().Version;
505 | var versionString = version.Major + "." + version.Minor + "." + version.Build;
506 |
507 | // Strings are not localized since they are not shown to the user.
508 | versionInfo.Version = 1;
509 | versionInfo.HasRealMergedData = true;
510 | versionInfo.ReaderName = "Paint.NET PSD Plugin";
511 | versionInfo.WriterName = "Paint.NET PSD Plugin " + versionString;
512 | versionInfo.FileVersion = 1;
513 | }
514 | }
515 |
516 | private void SaveLayers(PsdBinaryWriter writer) {
517 | using (new PsdBlockLengthWriter(writer)) {
518 | var numLayers = (Int16)Layers.Count;
519 | if (AbsoluteAlpha)
520 | numLayers = (Int16)(-numLayers);
521 |
522 | // Layers section must be empty if the color mode doesn't allow layers.
523 | // Photoshop will refuse to load indexed and multichannel images if
524 | // there is a nonempty layers section with a layer count of 0.
525 | if (numLayers == 0)
526 | return;
527 |
528 | var startPosition = writer.BaseStream.Position;
529 | writer.Write(numLayers);
530 |
531 | foreach (var layer in Layers) {
532 | layer.Save(writer);
533 | }
534 |
535 | foreach (var layer in Layers) {
536 | foreach (var channel in layer.Channels) {
537 | channel.SavePixelData(writer);
538 | }
539 | }
540 |
541 | // Documentation states that the Layers Info section is even-padded,
542 | // but it is actually padded to a multiple of 4.
543 | writer.WritePadding(startPosition, 4);
544 | }
545 | }
546 |
547 | ///////////////////////////////////////////////////////////////////////////
548 |
549 | byte[] GlobalLayerMaskData = new byte[0];
550 |
551 | private void LoadGlobalLayerMask(PsdBinaryReader reader) {
552 | var maskLength = reader.ReadUInt32();
553 | if (maskLength <= 0)
554 | return;
555 |
556 | GlobalLayerMaskData = reader.ReadBytes((int)maskLength);
557 | }
558 |
559 | ///////////////////////////////////////////////////////////////////////////
560 |
561 | private void SaveGlobalLayerMask(PsdBinaryWriter writer) {
562 | writer.Write((UInt32)GlobalLayerMaskData.Length);
563 | writer.Write(GlobalLayerMaskData);
564 | }
565 |
566 | ///////////////////////////////////////////////////////////////////////////
567 |
568 | #endregion
569 |
570 | ///////////////////////////////////////////////////////////////////////////
571 |
572 | #region ImageData
573 |
574 | ///////////////////////////////////////////////////////////////////////////
575 |
576 | private void LoadImage(PsdBinaryReader reader) {
577 | ImageCompression = (ImageCompression)reader.ReadInt16();
578 |
579 | // Create channels
580 | for (Int16 i = 0; i < ChannelCount; i++) {
581 | var channel = new Channel(i, this.BaseLayer);
582 | channel.ImageCompression = ImageCompression;
583 | channel.Length = this.RowCount * Util.BytesPerRow(BaseLayer.Rect, BitDepth);
584 |
585 | // The composite image stores all RLE headers up-front, rather than
586 | // with each channel.
587 | if (ImageCompression == ImageCompression.Rle) {
588 | channel.RleRowLengths = new RleRowLengths(reader, RowCount);
589 | channel.Length = channel.RleRowLengths.Total;
590 | }
591 |
592 | BaseLayer.Channels.Add(channel);
593 | }
594 |
595 | foreach (var channel in this.BaseLayer.Channels) {
596 | channel.ImageDataRaw = reader.ReadBytes(channel.Length);
597 | }
598 |
599 | // If there is exactly one more channel than we need, then it is the
600 | // alpha channel.
601 | if ((ColorMode != PsdColorMode.Multichannel)
602 | && (ChannelCount == ColorMode.MinChannelCount() + 1)) {
603 | var alphaChannel = BaseLayer.Channels.Last();
604 | alphaChannel.ID = -1;
605 | }
606 | }
607 |
608 | ///////////////////////////////////////////////////////////////////////////
609 |
610 | private void SaveImage(PsdBinaryWriter writer) {
611 | writer.Write((short)this.ImageCompression);
612 | if (this.ImageCompression == PhotoshopFile.ImageCompression.Rle) {
613 | foreach (var channel in this.BaseLayer.Channels)
614 | channel.RleRowLengths.Write(writer);
615 | }
616 | foreach (var channel in this.BaseLayer.Channels) {
617 | writer.Write(channel.ImageDataRaw);
618 | }
619 | }
620 |
621 | ///////////////////////////////////////////////////////////////////////////
622 |
623 | private class DecompressChannelContext {
624 | private Channel ch;
625 |
626 | public DecompressChannelContext(Channel ch) {
627 | this.ch = ch;
628 | }
629 |
630 | public void DecompressChannel(object context) {
631 | ch.DecodeImageData();
632 | }
633 | }
634 |
635 | #endregion
636 | }
637 |
638 |
639 | ///
640 | /// The possible Compression methods.
641 | ///
642 | public enum ImageCompression {
643 | ///
644 | /// Raw data
645 | ///
646 | Raw = 0,
647 | ///
648 | /// RLE compressed
649 | ///
650 | Rle = 1,
651 | ///
652 | /// ZIP without prediction.
653 | ///
654 | Zip = 2,
655 | ///
656 | /// ZIP with prediction.
657 | ///
658 | ZipPrediction = 3
659 | }
660 |
661 | }
662 |
--------------------------------------------------------------------------------