├── .gitattributes ├── .gitignore ├── License.txt ├── PSDPlugin.sln ├── PhotoShopFileType ├── Editor │ └── PSDEditorWindow.cs ├── PhotoShop.csproj ├── Properties │ └── AssemblyInfo.cs ├── PsdFile │ ├── Exceptions.cs │ ├── ImageResource.cs │ ├── ImageResources │ │ ├── AlphaChannelNames.cs │ │ ├── RawImageResource.cs │ │ ├── ResolutionInfo.cs │ │ ├── Thumbnail.cs │ │ ├── UnicodeAlphaNames.cs │ │ └── VersionInfo.cs │ ├── Layers │ │ ├── BlendingRanges.cs │ │ ├── Channel.cs │ │ ├── Layer.cs │ │ ├── LayerInfo.cs │ │ ├── LayerInfo │ │ │ ├── LayerSectionInfo.cs │ │ │ ├── LayerUnicodeName.cs │ │ │ └── RawLayerInfo.cs │ │ └── Mask.cs │ ├── PsdBinaryReader.cs │ ├── PsdBinaryWriter.cs │ ├── PsdBlendMode.cs │ ├── PsdBlockLengthWriter.cs │ ├── PsdFile.cs │ ├── RleReader.cs │ ├── RleRowLengths.cs │ ├── RleWriter.cs │ └── Util.cs ├── Resources.resx ├── gmcs.rsp └── smcs.rsp ├── README.md └── UnityPsdImporter.asmdef /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Editor/PSDEditorWindow.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013-2022 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 | private bool allVisible = true; 44 | 45 | private Transform selectedTransform; 46 | 47 | [MenuItem("Sprites/PSD Import")] 48 | public static void ShowWindow() { 49 | var wnd = GetWindow(); 50 | wnd.title = "PSD Import"; 51 | wnd.Show(); 52 | } 53 | 54 | public void OnGUI() { 55 | EditorGUI.BeginChangeCheck(); 56 | image = (Texture2D)EditorGUILayout.ObjectField("PSD File", image, typeof(Texture2D), true); 57 | bool changed = EditorGUI.EndChangeCheck(); 58 | 59 | if (image != null) { 60 | if (changed) { 61 | string path = AssetDatabase.GetAssetPath(image); 62 | 63 | if (path.ToUpper().EndsWith(".PSD")) { 64 | psd = new PsdFile(path, Encoding.Default); 65 | fileName = Path.GetFileNameWithoutExtension(path); 66 | } 67 | else { 68 | psd = null; 69 | } 70 | } 71 | 72 | if (psd != null) { 73 | if (GUILayout.Button("Toggle All Layers Visible")) { 74 | ToggleVisibility(); 75 | } 76 | 77 | scrollPos = EditorGUILayout.BeginScrollView(scrollPos); 78 | 79 | foreach (Layer layer in psd.Layers) { 80 | if (layer.Name != "" && layer.Name != "") { 81 | layer.Visible = EditorGUILayout.ToggleLeft(layer.Name, layer.Visible); 82 | } 83 | } 84 | 85 | EditorGUILayout.EndScrollView(); 86 | 87 | if (GUILayout.Button("Export visible layers")) { 88 | ExportLayers(); 89 | } 90 | 91 | atlassize = EditorGUILayout.IntField("Max. atlas size", atlassize); 92 | 93 | if (!((atlassize != 0) && ((atlassize & (atlassize - 1)) == 0))) { 94 | EditorGUILayout.HelpBox("Atlas size should be a power of 2", MessageType.Warning); 95 | } 96 | 97 | pixelsToUnitSize = EditorGUILayout.FloatField("Pixels To Unit Size", pixelsToUnitSize); 98 | 99 | if (pixelsToUnitSize <= 0) { 100 | EditorGUILayout.HelpBox("Pixels To Unit Size should be greater than 0.", MessageType.Warning); 101 | } 102 | 103 | importIntoSelected = EditorGUILayout.Toggle("Import into selected object", importIntoSelected); 104 | useSizeDelta = EditorGUILayout.Toggle("Use Size Delta", useSizeDelta); 105 | 106 | if (GUILayout.Button("Create atlas")) { 107 | CreateAtlas(); 108 | } 109 | if (GUILayout.Button("Create sprites")) { 110 | CreateSprites(); 111 | } 112 | if (GUILayout.Button("Create images")) { 113 | CreateImages(); 114 | } 115 | } 116 | else { 117 | EditorGUILayout.HelpBox("This texture is not a PSD file.", MessageType.Error); 118 | } 119 | } 120 | } 121 | 122 | private Texture2D CreateTexture(Layer layer) { 123 | if ((int)layer.Rect.width == 0 || (int)layer.Rect.height == 0) 124 | return null; 125 | 126 | Texture2D tex = new Texture2D((int)layer.Rect.width, (int)layer.Rect.height, TextureFormat.RGBA32, true); 127 | Color32[] pixels = new Color32[tex.width * tex.height]; 128 | 129 | Channel red = (from l in layer.Channels where l.ID == 0 select l).First(); 130 | Channel green = (from l in layer.Channels where l.ID == 1 select l).First(); 131 | Channel blue = (from l in layer.Channels where l.ID == 2 select l).First(); 132 | Channel alpha = layer.AlphaChannel; 133 | 134 | for (int i = 0; i < pixels.Length; i++) { 135 | byte r = red.ImageData[i]; 136 | byte g = green.ImageData[i]; 137 | byte b = blue.ImageData[i]; 138 | byte a = 255; 139 | 140 | if (alpha != null) 141 | a = alpha.ImageData[i]; 142 | 143 | int mod = i % tex.width; 144 | int n = ((tex.width - mod - 1) + i) - mod; 145 | pixels[pixels.Length - n - 1] = new Color32(r, g, b, a); 146 | } 147 | 148 | tex.SetPixels32(pixels); 149 | tex.Apply(); 150 | return tex; 151 | } 152 | 153 | private void ExportLayers() { 154 | foreach (Layer layer in psd.Layers) { 155 | if (layer.Visible) { 156 | Texture2D tex = CreateTexture(layer); 157 | if (tex == null) continue; 158 | SaveAsset(tex, "_" + layer.Name); 159 | DestroyImmediate(tex); 160 | } 161 | } 162 | } 163 | 164 | private void CreateAtlas() { 165 | // Texture2D[] textures = (from layer in psd.Layers where layer.Visible select CreateTexture(layer) into tex where tex != null select tex).ToArray(); 166 | 167 | List textures = new List(); 168 | 169 | // Track the spriteRenderers created via a List 170 | List spriteRenderers = new List(); 171 | 172 | int zOrder = 0; 173 | GameObject root = new GameObject(fileName); 174 | foreach (var layer in psd.Layers) { 175 | if (layer.Visible && layer.Rect.width > 0 && layer.Rect.height > 0) { 176 | Texture2D tex = CreateTexture(layer); 177 | // Add the texture to the Texture Array 178 | textures.Add(tex); 179 | 180 | GameObject go = new GameObject(layer.Name); 181 | SpriteRenderer sr = go.AddComponent(); 182 | go.transform.position = new Vector3((layer.Rect.width / 2 + layer.Rect.x) / pixelsToUnitSize, (-layer.Rect.height / 2 - layer.Rect.y) / pixelsToUnitSize, 0); 183 | // Add the sprite renderer to the SpriteRenderer Array 184 | spriteRenderers.Add(sr); 185 | sr.sortingOrder = zOrder++; 186 | go.transform.parent = root.transform; 187 | } 188 | } 189 | 190 | // The output of PackTextures returns a Rect array from which we can create our sprites 191 | Rect[] rects; 192 | Texture2D atlas = new Texture2D(atlassize, atlassize); 193 | Texture2D[] textureArray = textures.ToArray(); 194 | rects = atlas.PackTextures(textureArray, 2, atlassize); 195 | List Sprites = new List(); 196 | 197 | // For each rect in the Rect Array create the sprite and assign to the SpriteMetaData 198 | for (int i = 0; i < rects.Length; i++) { 199 | // add the name and rectangle to the dictionary 200 | SpriteMetaData smd = new SpriteMetaData(); 201 | smd.name = spriteRenderers[i].name; 202 | smd.rect = new Rect(rects[i].xMin * atlas.width, rects[i].yMin * atlas.height, rects[i].width * atlas.width, rects[i].height * atlas.height); 203 | smd.pivot = new Vector2(0.5f, 0.5f); // Center is default otherwise layers will be misaligned 204 | smd.alignment = (int)SpriteAlignment.Center; 205 | Sprites.Add(smd); 206 | } 207 | 208 | // Need to load the image first 209 | string assetPath = AssetDatabase.GetAssetPath(image); 210 | string path = Path.Combine(Path.GetDirectoryName(assetPath), 211 | Path.GetFileNameWithoutExtension(assetPath) + "_atlas" + ".png"); 212 | 213 | byte[] buf = atlas.EncodeToPNG(); 214 | File.WriteAllBytes(path, buf); 215 | AssetDatabase.Refresh(); 216 | 217 | // Get our texture that we loaded 218 | atlas = (Texture2D)AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D)); 219 | TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; 220 | // Make sure the size is the same as our atlas then create the spritesheet 221 | textureImporter.maxTextureSize = atlassize; 222 | textureImporter.spritesheet = Sprites.ToArray(); 223 | textureImporter.textureType = TextureImporterType.Sprite; 224 | textureImporter.spriteImportMode = SpriteImportMode.Multiple; 225 | textureImporter.spritePivot = new Vector2(0.5f, 0.5f); 226 | textureImporter.spritePixelsPerUnit = pixelsToUnitSize; 227 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); 228 | 229 | // For each rect in the Rect Array create the sprite and assign to the SpriteRenderer 230 | for (int j = 0; j < textureImporter.spritesheet.Length; j++) { 231 | // Debug.Log(textureImporter.spritesheet[j].rect); 232 | 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? 233 | 234 | // Add the sprite to the sprite renderer 235 | spriteRenderers[j].sprite = spr; 236 | } 237 | 238 | foreach (Texture2D tex in textureArray) { 239 | DestroyImmediate(tex); 240 | } 241 | } 242 | 243 | private void CreateSprites() { 244 | if (importIntoSelected) { 245 | selectedTransform = Selection.activeTransform; 246 | } 247 | int zOrder = 0; 248 | GameObject root = new GameObject(fileName); 249 | if (importIntoSelected && selectedTransform != null) { 250 | root.transform.parent = selectedTransform; 251 | } 252 | foreach (var layer in psd.Layers) { 253 | if (layer.Visible && layer.Rect.width > 0 && layer.Rect.height > 0) { 254 | Texture2D tex = CreateTexture(layer); 255 | Sprite spr = SaveAsset(tex, "_" + layer.Name); 256 | DestroyImmediate(tex); 257 | 258 | GameObject go = new GameObject(layer.Name); 259 | SpriteRenderer sr = go.AddComponent(); 260 | sr.sprite = spr; 261 | sr.sortingOrder = zOrder++; 262 | go.transform.position = new Vector3((layer.Rect.width / 2 + layer.Rect.x) / pixelsToUnitSize, (-layer.Rect.height / 2 - layer.Rect.y) / pixelsToUnitSize, 0); 263 | go.transform.parent = root.transform; 264 | } 265 | } 266 | } 267 | private void CreateImages() { 268 | if (importIntoSelected) { 269 | selectedTransform = Selection.activeTransform; 270 | } 271 | int zOrder = 0; 272 | GameObject root = new GameObject(fileName); 273 | root.transform.localPosition = new Vector3(0, 0, 0); 274 | var rtransf = root.AddComponent(); 275 | if (importIntoSelected && selectedTransform != null) 276 | root.transform.parent = selectedTransform; 277 | rtransf.anchorMin = new Vector2(0f, 0f); 278 | rtransf.anchorMax = new Vector2(1f, 1f); 279 | rtransf.pivot = new Vector2(0.5f, 0.5f); 280 | rtransf.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0); 281 | rtransf.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0); 282 | rtransf.sizeDelta = Vector2.zero; 283 | rtransf.localPosition = Vector3.zero; 284 | 285 | foreach (var layer in psd.Layers) { 286 | if (layer.Visible && layer.Rect.width > 0 && layer.Rect.height > 0) { 287 | var targetOrder = zOrder++; 288 | Texture2D tex = CreateTexture(layer); 289 | Sprite spr = SaveAsset(tex, "_" + layer.Name); 290 | DestroyImmediate(tex); 291 | 292 | GameObject go = new GameObject(layer.Name); 293 | go.transform.parent = root.transform; 294 | go.transform.SetSiblingIndex(targetOrder); 295 | UnityEngine.UI.Image image = go.AddComponent(); 296 | image.sprite = spr; 297 | image.rectTransform.localPosition = new Vector3((layer.Rect.x), (layer.Rect.y * -1) - layer.Rect.height, targetOrder * 5); 298 | image.rectTransform.anchorMax = new Vector2(0, 1); 299 | image.rectTransform.anchorMin = new Vector2(0, 1); 300 | image.rectTransform.pivot = new Vector2(0f, 0f); 301 | image.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, layer.Rect.width); 302 | image.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, layer.Rect.height); 303 | } 304 | } 305 | } 306 | 307 | private Sprite SaveAsset(Texture2D tex, string suffix) { 308 | string assetPath = AssetDatabase.GetAssetPath(image); 309 | string path = Path.Combine(Path.GetDirectoryName(assetPath), 310 | Path.GetFileNameWithoutExtension(assetPath) + suffix + ".png"); 311 | 312 | byte[] buf = tex.EncodeToPNG(); 313 | File.WriteAllBytes(path, buf); 314 | AssetDatabase.Refresh(); 315 | // Load the texture so we can change the type 316 | AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D)); 317 | TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; 318 | textureImporter.textureType = TextureImporterType.Sprite; 319 | textureImporter.spriteImportMode = SpriteImportMode.Single; 320 | textureImporter.spritePivot = new Vector2(0.5f, 0.5f); 321 | textureImporter.spritePixelsPerUnit = pixelsToUnitSize; 322 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); 323 | 324 | return (Sprite)AssetDatabase.LoadAssetAtPath(path, typeof(Sprite)); 325 | } 326 | 327 | private void ToggleVisibility() { 328 | allVisible = !allVisible; 329 | 330 | foreach (Layer layer in psd.Layers) { 331 | if (layer.Name != "" && layer.Name != "") { 332 | layer.Visible = allVisible; 333 | } 334 | } 335 | } 336 | 337 | } 338 | 339 | -------------------------------------------------------------------------------- /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/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 | // '*' does not work in Unity 2020 so must do manually: 47 | [assembly: AssemblyVersion("2.4.0.0")] 48 | [assembly: AssemblyFileVersion("2.4.0.0")] 49 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 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/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 | -------------------------------------------------------------------------------- /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/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/PsdFile/Layers/LayerInfo.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 | using System.IO; 17 | 18 | namespace PhotoshopFile 19 | { 20 | public static class LayerInfoFactory 21 | { 22 | public static LayerInfo Load(PsdBinaryReader reader) 23 | { 24 | Debug.WriteLine("LayerInfoFactory.Load started at " + reader.BaseStream.Position); 25 | 26 | var signature = reader.ReadAsciiChars(4); 27 | if (signature != "8BIM") 28 | throw new PsdInvalidException("Could not read LayerInfo due to signature mismatch."); 29 | 30 | var key = reader.ReadAsciiChars(4); 31 | var length = reader.ReadInt32(); 32 | var startPosition = reader.BaseStream.Position; 33 | 34 | LayerInfo result; 35 | switch (key) 36 | { 37 | case "lsct": 38 | case "lsdk": 39 | result = new LayerSectionInfo(reader, key, length); 40 | break; 41 | case "luni": 42 | result = new LayerUnicodeName(reader); 43 | break; 44 | default: 45 | result = new RawLayerInfo(reader, key, length); 46 | break; 47 | } 48 | 49 | // May have additional padding applied. 50 | var endPosition = startPosition + length; 51 | if (reader.BaseStream.Position < endPosition) 52 | reader.BaseStream.Position = endPosition; 53 | 54 | // Documentation states that the length is even-padded. Actually: 55 | // 1. Most keys have 4-padded lengths. 56 | // 2. However, some keys (LMsk) have even-padded lengths. 57 | // 3. Other keys (Txt2, Lr16, Lr32) have unpadded lengths. 58 | // 59 | // The data is always 4-padded, regardless of the stated length. 60 | 61 | reader.ReadPadding(startPosition, 4); 62 | 63 | return result; 64 | } 65 | } 66 | 67 | public abstract class LayerInfo 68 | { 69 | public abstract string Key { get; } 70 | 71 | protected abstract void WriteData(PsdBinaryWriter writer); 72 | 73 | public void Save(PsdBinaryWriter writer) 74 | { 75 | Debug.WriteLine("LayerInfo.Save started at " + writer.BaseStream.Position); 76 | 77 | writer.WriteAsciiChars("8BIM"); 78 | writer.WriteAsciiChars(Key); 79 | 80 | var startPosition = writer.BaseStream.Position; 81 | using (var lengthWriter = new PsdBlockLengthWriter(writer)) 82 | { 83 | // Depending on the key, the length may be unpadded, 2-padded, or 84 | // 4-padded. Thus, it is up to each implementation of WriteData to 85 | // pad the length correctly. 86 | WriteData(writer); 87 | } 88 | 89 | // Regardless of how the length is padded, the data is always padded to 90 | // a multiple of 4. 91 | writer.WritePadding(startPosition, 4); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /PhotoShopFileType/PsdFile/RleReader.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 RleReader 25 | { 26 | private Stream stream; 27 | 28 | public RleReader(Stream stream) 29 | { 30 | this.stream = stream; 31 | } 32 | 33 | /// 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/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/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/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/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/gmcs.rsp: -------------------------------------------------------------------------------- 1 | -unsafe 2 | -------------------------------------------------------------------------------- /PhotoShopFileType/smcs.rsp: -------------------------------------------------------------------------------- 1 | -unsafe 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /UnityPsdImporter.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UnityPsdImporter", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": true, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [] 14 | } --------------------------------------------------------------------------------