///////////////////////////////////////////////////////////////////////////////// // // Photoshop PSD FileType Plugin for Paint.NET // http://psdplugin.codeplex.com/ // // This software is provided under the MIT License: // Copyright (c) 2006-2007 Frank Blumenberg // Copyright (c) 2010-2016 Tao Yue // // Portions of this file are provided under the BSD 3-clause License: // Copyright (c) 2006, Jonas Beckeman // // See LICENSE.txt for complete licensing and attribution information. // ///////////////////////////////////////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Diagnostics; using PDNWrapper; using System.IO; using System.Linq; using System.Text; using PaintDotNet.Data.PhotoshopFileType; namespace PhotoshopFile { [Flags] internal enum ELoadFlag { Header = 1, ColorMode = Header | 1 << 2, ImageData = ColorMode | 1 << 3, All = Header | ColorMode | ImageData } internal enum PsdColorMode { Bitmap = 0, Grayscale = 1, Indexed = 2, RGB = 3, CMYK = 4, Multichannel = 7, Duotone = 8, Lab = 9 }; internal enum PsdFileVersion : short { Psd = 1, PsbLargeDocument = 2 } internal class PsdFile { #region Constructors ELoadFlag m_LoadFlag; public PsdFile(PsdFileVersion version = PsdFileVersion.Psd) { Version = version; BaseLayer = new Layer(this); ImageResources = new ImageResources(); Layers = new List(); AdditionalInfo = new List(); } public PsdFile(string filename, LoadContext loadContext, ELoadFlag loadFlag = ELoadFlag.All) : this() { using (var stream = new FileStream(filename, FileMode.Open)) { Load(stream, loadContext, loadFlag); } } public PsdFile(Stream stream, LoadContext loadContext, ELoadFlag loadFlag = ELoadFlag.All) : this() { Load(stream, loadContext, loadFlag); } #endregion #region Load and save internal LoadContext LoadContext { get; private set; } private void Load(Stream stream, LoadContext loadContext, ELoadFlag loadFlag) { LoadContext = loadContext; var reader = new PsdBinaryReader(stream, loadContext.Encoding); if ((loadFlag & ELoadFlag.Header) == ELoadFlag.Header) LoadHeader(reader); if ((loadFlag & ELoadFlag.ColorMode) == ELoadFlag.ColorMode) LoadColorModeData(reader); if ((loadFlag & ELoadFlag.ImageData) == ELoadFlag.ImageData) { LoadImageResources(reader); LoadLayerAndMaskInfo(reader); LoadImage(reader); DecompressImages(); } } #endregion #region Header /// /// Photoshop file format version. /// public PsdFileVersion Version { get; private set; } public bool IsLargeDocument { get { return Version == PsdFileVersion.PsbLargeDocument; } } private Int16 channelCount; /// /// The number of channels in the image, including any alpha channels. /// public Int16 ChannelCount { get { return channelCount; } set { if (value < 1 || value > 56) throw new ArgumentException("Number of channels must be from 1 to 56."); channelCount = value; } } private void CheckDimension(int dimension) { if (dimension < 1) { throw new ArgumentException("Image dimension must be at least 1."); } if ((Version == PsdFileVersion.Psd) && (dimension > 30000)) { throw new ArgumentException("PSD image dimension cannot exceed 30000."); } if ((Version == PsdFileVersion.PsbLargeDocument) && (dimension > 300000)) { throw new ArgumentException("PSB image dimension cannot exceed 300000."); } } /// /// The height of the image in pixels. /// public int RowCount { get { return this.BaseLayer.Rect.Height; } set { CheckDimension(value); BaseLayer.Rect = new Rectangle(0, 0, BaseLayer.Rect.Width, value); } } /// /// The width of the image in pixels. /// public int ColumnCount { get { return this.BaseLayer.Rect.Width; } set { CheckDimension(value); BaseLayer.Rect = new Rectangle(0, 0, value, BaseLayer.Rect.Height); } } private int bitDepth; /// /// The number of bits per channel. Supported values are 1, 8, 16, and 32. /// public int BitDepth { get { return bitDepth; } set { switch (value) { case 1: case 8: case 16: case 32: bitDepth = value; break; default: throw new NotImplementedException("Invalid bit depth."); } } } /// /// The color mode of the file. /// public PsdColorMode ColorMode { get; set; } /////////////////////////////////////////////////////////////////////////// private void LoadHeader(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, File header"); var signature = reader.ReadAsciiChars(4); if (signature != "8BPS") throw new PsdInvalidException("The given stream is not a valid PSD file"); Version = (PsdFileVersion)reader.ReadInt16(); Util.DebugMessage(reader.BaseStream, "Load, Info, Version {0}", (int)Version); if ((Version != PsdFileVersion.Psd) && (Version != PsdFileVersion.PsbLargeDocument)) { throw new PsdInvalidException("The PSD file has an unknown version"); } //6 bytes reserved reader.BaseStream.Position += 6; this.ChannelCount = reader.ReadInt16(); this.RowCount = reader.ReadInt32(); this.ColumnCount = reader.ReadInt32(); BitDepth = reader.ReadInt16(); ColorMode = (PsdColorMode)reader.ReadInt16(); Util.DebugMessage(reader.BaseStream, "Load, End, File header"); } #endregion /////////////////////////////////////////////////////////////////////////// #region ColorModeData /// /// If ColorMode is ColorModes.Indexed, the following 768 bytes will contain /// a 256-color palette. If the ColorMode is ColorModes.Duotone, the data /// following presumably consists of screen parameters and other related information. /// Unfortunately, it is intentionally not documented by Adobe, and non-Photoshop /// readers are advised to treat duotone images as gray-scale images. /// public byte[] ColorModeData = new byte[0]; private void LoadColorModeData(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, ColorModeData"); var paletteLength = reader.ReadUInt32(); if (paletteLength > 0) { ColorModeData = reader.ReadBytes((int)paletteLength); } Util.DebugMessage(reader.BaseStream, "Load, End, ColorModeData"); } #endregion /////////////////////////////////////////////////////////////////////////// #region ImageResources /// /// The Image resource blocks for the file /// public ImageResources ImageResources { get; set; } public ResolutionInfo Resolution { get { return (ResolutionInfo)ImageResources.Get(ResourceID.ResolutionInfo); } set { ImageResources.Set(value); } } /////////////////////////////////////////////////////////////////////////// private void LoadImageResources(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, ImageResources"); var imageResourcesLength = reader.ReadUInt32(); if (imageResourcesLength <= 0) return; var startPosition = reader.BaseStream.Position; var endPosition = startPosition + imageResourcesLength; while (reader.BaseStream.Position < endPosition) { var imageResource = ImageResourceFactory.CreateImageResource(reader); ImageResources.Add(imageResource); } Util.DebugMessage(reader.BaseStream, "Load, End, ImageResources"); //----------------------------------------------------------------------- // make sure we are not on a wrong offset, so set the stream position // manually reader.BaseStream.Position = startPosition + imageResourcesLength; } #endregion /////////////////////////////////////////////////////////////////////////// #region LayerAndMaskInfo public List Layers { get; private set; } public List AdditionalInfo { get; private set; } public bool AbsoluteAlpha { get; set; } /////////////////////////////////////////////////////////////////////////// private void LoadLayerAndMaskInfo(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Layer and mask info"); var layersAndMaskLength = IsLargeDocument ? reader.ReadInt64() : reader.ReadUInt32(); if (layersAndMaskLength <= 0) return; var startPosition = reader.BaseStream.Position; var endPosition = startPosition + layersAndMaskLength; LoadLayers(reader, true); LoadGlobalLayerMask(reader); //----------------------------------------------------------------------- // Load Additional Layer Information while (reader.BaseStream.Position < endPosition) { var info = LayerInfoFactory.Load(reader, this, true, endPosition); AdditionalInfo.Add(info); if (info is RawLayerInfo) { var layerInfo = (RawLayerInfo)info; switch (info.Key) { case "LMsk": m_GlobalLayerMaskData = layerInfo.Data; break; } } } Util.DebugMessage(reader.BaseStream, "Load, End, Layer and mask info"); //----------------------------------------------------------------------- // make sure we are not on a wrong offset, so set the stream position // manually reader.BaseStream.Position = startPosition + layersAndMaskLength; } /////////////////////////////////////////////////////////////////////////// /// /// Load Layers Info section, including image data. /// /// PSD reader. /// Whether the Layers Info section has a length header. internal void LoadLayers(PsdBinaryReader reader, bool hasHeader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Layers Info section"); long sectionLength = 0; if (hasHeader) { sectionLength = IsLargeDocument ? reader.ReadInt64() : reader.ReadUInt32(); if (sectionLength <= 0) { // The callback may take action when there are 0 layers, so it must // be called even though the Layers Info section is empty. LoadContext.OnLoadLayersHeader(this); Util.DebugMessage(reader.BaseStream, "Load, End, Layers Info section"); return; } } var startPosition = reader.BaseStream.Position; var numLayers = reader.ReadInt16(); // If numLayers < 0, then number of layers is absolute value, // and the first alpha channel contains the transparency data for // the merged result. if (numLayers < 0) { AbsoluteAlpha = true; numLayers = Math.Abs(numLayers); } for (int i = 0; i < numLayers; i++) { var layer = new Layer(reader, this); Layers.Add(layer); } // Header is complete just before loading pixel data LoadContext.OnLoadLayersHeader(this); //----------------------------------------------------------------------- // Load image data for all channels. foreach (var layer in Layers) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Layer image, layer.Name"); foreach (var channel in layer.Channels) { channel.LoadPixelData(reader); } Util.DebugMessage(reader.BaseStream, "Load, End, Layer image, layer.Name"); } // Length is set to 0 when called on higher bitdepth layers. if (sectionLength > 0) { // Layers Info section is documented to be even-padded, but Photoshop // actually pads to 4 bytes. var endPosition = startPosition + sectionLength; var positionOffset = reader.BaseStream.Position - endPosition; Debug.Assert(positionOffset > -4, "LoadLayers did not read the full length of the Layers Info section."); Debug.Assert(positionOffset <= 0, "LoadLayers read past the end of the Layers Info section."); if (reader.BaseStream.Position < endPosition) reader.BaseStream.Position = endPosition; } Util.DebugMessage(reader.BaseStream, "Load, End, Layers"); } /////////////////////////////////////////////////////////////////////////// /// /// Cleanup /// public void Cleanup() { var layersAndComposite = Layers.Concat(new[] { BaseLayer }); foreach (var lac in layersAndComposite) { foreach (var c in lac.Channels) { c.Cleanup(); } } } /////////////////////////////////////////////////////////////////////////// /// /// Decompress the document image data and all the layers' image data, in parallel. /// private void DecompressImages() { var layersAndComposite = Layers.Concat(new[] { BaseLayer }); //var channels = layersAndComposite.SelectMany(x => x.Channels); //Parallel.ForEach(channels, channel => //{ // channel.DecodeImageData(); //}); foreach (var lac in layersAndComposite) { foreach (var c in lac.Channels) { c.DecodeImageData(); } } foreach (var layer in Layers) { foreach (var channel in layer.Channels) { if (channel.ID == -2) layer.Masks.LayerMask.ImageData = channel.ImageData; else if (channel.ID == -3) layer.Masks.UserMask.ImageData = channel.ImageData; } } } /// /// Verifies that any Additional Info layers are consistent. /// private void VerifyInfoLayers() { var infoLayersCount = AdditionalInfo.Count(x => x is InfoLayers); if (infoLayersCount > 1) { throw new PsdInvalidException( "Cannot have more than one InfoLayers in a PSD file."); } if ((infoLayersCount > 0) && (Layers.Count == 0)) { throw new PsdInvalidException( "InfoLayers cannot exist when there are 0 layers."); } } /// /// Verify validity of layer sections. Each start marker should have a /// matching end marker. /// internal void VerifyLayerSections() { int depth = 0; foreach (var layer in Enumerable.Reverse(Layers)) { var layerSectionInfo = layer.AdditionalInfo.SingleOrDefault( x => x is LayerSectionInfo); if (layerSectionInfo == null) continue; var sectionInfo = (LayerSectionInfo)layerSectionInfo; switch (sectionInfo.SectionType) { case LayerSectionType.OpenFolder: case LayerSectionType.ClosedFolder: depth++; break; case LayerSectionType.SectionDivider: depth--; if (depth < 0) throw new PsdInvalidException("Layer section ended without matching start marker."); break; case LayerSectionType.Layer: // Nothing to do here yet. break; default: throw new PsdInvalidException("Unrecognized layer section type."); } } if (depth != 0) throw new PsdInvalidException("Layer section not closed by end marker."); } /// /// Set the VersionInfo resource on the file. /// public void SetVersionInfo() { var versionInfo = (VersionInfo)ImageResources.Get(ResourceID.VersionInfo); if (versionInfo == null) { versionInfo = new VersionInfo(); ImageResources.Set(versionInfo); // Get the version string. We don't use the fourth part (revision). var assembly = System.Reflection.Assembly.GetExecutingAssembly(); var version = assembly.GetName().Version; var versionString = version.Major + "." + version.Minor + "." + version.Build; // Strings are not localized since they are not shown to the user. versionInfo.Version = 1; versionInfo.HasRealMergedData = true; versionInfo.ReaderName = "Paint.NET PSD Plugin"; versionInfo.WriterName = "Paint.NET PSD Plugin " + versionString; versionInfo.FileVersion = 1; } } /////////////////////////////////////////////////////////////////////////// byte[] m_GlobalLayerMaskData; private void LoadGlobalLayerMask(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, GlobalLayerMask"); var maskLength = reader.ReadUInt32(); if (maskLength <= 0) { Util.DebugMessage(reader.BaseStream, "Load, End, GlobalLayerMask"); return; } m_GlobalLayerMaskData = reader.ReadBytes((int)maskLength); Util.DebugMessage(reader.BaseStream, "Load, End, GlobalLayerMask"); } public byte[] globalLayerMaskData { get { return m_GlobalLayerMaskData; } } #endregion /////////////////////////////////////////////////////////////////////////// #region Composite image /// /// Represents the composite image. /// public Layer BaseLayer { get; set; } public ImageCompression ImageCompression { get; set; } private void LoadImage(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Composite image"); ImageCompression = (ImageCompression)reader.ReadInt16(); // Create channels for (Int16 i = 0; i < ChannelCount; i++) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel image data"); var channel = new Channel(i, this.BaseLayer); channel.ImageCompression = ImageCompression; channel.Length = this.RowCount * Util.BytesPerRow(BaseLayer.Rect.Size, BitDepth); // The composite image stores all RLE headers up-front, rather than // with each channel. if (ImageCompression == ImageCompression.Rle) { channel.RleRowLengths = new RleRowLengths(reader, RowCount, IsLargeDocument); channel.Length = channel.RleRowLengths.Total; } BaseLayer.Channels.Add(channel); Util.DebugMessage(reader.BaseStream, "Load, End, Channel image data"); } foreach (var channel in this.BaseLayer.Channels) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel image data"); Util.CheckByteArrayLength(channel.Length); channel.ImageDataRaw = reader.ReadBytes((int)channel.Length); Util.DebugMessage(reader.BaseStream, "Load, End, Channel image data"); } // If there is exactly one more channel than we need, then it is the // alpha channel. if ((ColorMode != PsdColorMode.Multichannel) && (ChannelCount == ColorMode.MinChannelCount() + 1)) { var alphaChannel = BaseLayer.Channels.Last(); alphaChannel.ID = -1; } Util.DebugMessage(reader.BaseStream, "Load, End, Composite image"); } #endregion } /// /// The possible Compression methods. /// internal enum ImageCompression { /// /// Raw data /// Raw = 0, /// /// RLE compressed /// Rle = 1, /// /// ZIP without prediction. /// Zip = 2, /// /// ZIP with prediction. /// ZipPrediction = 3 } }