///////////////////////////////////////////////////////////////////////////////// // // 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.Linq; using Unity.Collections; using PhotoshopFile.Compression; namespace PhotoshopFile { internal class ChannelList : List { /// /// Returns channels with nonnegative IDs as an array, so that accessing /// a channel by Id can be optimized into pointer arithmetic rather than /// being implemented as a List scan. /// /// /// This optimization is crucial for blitting lots of pixels back and /// forth between Photoshop's per-channel representation, and Paint.NET's /// per-pixel BGRA representation. /// public Channel[] ToIdArray() { var maxId = this.Max(x => x.ID); var idArray = new Channel[maxId + 1]; foreach (var channel in this) { if (channel.ID >= 0) idArray[channel.ID] = channel; } return idArray; } public ChannelList() : base() { } public Channel GetId(int id) { return this.Single(x => x.ID == id); } public bool ContainsId(int id) { return this.Exists(x => x.ID == id); } } /////////////////////////////////////////////////////////////////////////// [DebuggerDisplay("ID = {ID}")] internal class Channel { /// /// The layer to which this channel belongs /// public Layer Layer { get; private set; } /// /// Channel ID. /// /// -1 = transparency mask /// -2 = user-supplied layer mask, or vector mask /// -3 = user-supplied layer mask, if channel -2 contains a vector mask /// /// Nonnegative channel IDs give the actual image channels, in the /// order defined by the colormode. For example, 0, 1, 2 = R, G, B. /// /// /// public short ID { get; set; } public Rectangle Rect { get { switch (ID) { case -2: return Layer.Masks.LayerMask.Rect; case -3: return Layer.Masks.UserMask.Rect; default: return Layer.Rect; } } } /// /// Total length of the channel data, including compression headers. /// public long Length { get; set; } /// /// Raw image data for this color channel, in compressed on-disk format. /// /// /// If null, the ImageData will be automatically compressed during save. /// public byte[] ImageDataRaw { get; set; } /// /// Decompressed image data for this color channel. /// /// /// When making changes to the ImageData, set ImageDataRaw to null so that /// the correct data will be compressed during save. /// public NativeArray ImageData { get; set; } /// /// Image compression method used. /// public ImageCompression ImageCompression { get; set; } /// /// RLE-compressed length of each row. /// public RleRowLengths RleRowLengths { get; set; } ////////////////////////////////////////////////////////////////// internal Channel(short id, Layer layer) { ID = id; Layer = layer; } internal Channel(PsdBinaryReader reader, Layer layer) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel"); ID = reader.ReadInt16(); Length = (layer.PsdFile.IsLargeDocument) ? reader.ReadInt64() : reader.ReadInt32(); Layer = layer; Util.DebugMessage(reader.BaseStream, "Load, End, Channel, {0}", ID); } internal void Cleanup() { if (ImageData.IsCreated) ImageData.Dispose(); } ////////////////////////////////////////////////////////////////// internal void LoadPixelData(PsdBinaryReader reader) { Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel image"); if (Length == 0) { ImageCompression = ImageCompression.Raw; ImageDataRaw = new byte[0]; return; } var endPosition = reader.BaseStream.Position + this.Length; ImageCompression = (ImageCompression)reader.ReadInt16(); var longDataLength = this.Length - 2; Util.CheckByteArrayLength(longDataLength); var dataLength = (int)longDataLength; switch (ImageCompression) { case ImageCompression.Raw: ImageDataRaw = reader.ReadBytes(dataLength); break; case ImageCompression.Rle: // RLE row lengths RleRowLengths = new RleRowLengths(reader, Rect.Height, Layer.PsdFile.IsLargeDocument); var rleDataLength = (int)(endPosition - reader.BaseStream.Position); Debug.Assert(rleDataLength == RleRowLengths.Total, "RLE row lengths do not sum to length of channel image data."); // The PSD specification states that rows are padded to even sizes. // However, Photoshop doesn't actually do this. RLE rows can have // odd lengths in the header, and there is no padding between rows. ImageDataRaw = reader.ReadBytes(rleDataLength); break; case ImageCompression.Zip: case ImageCompression.ZipPrediction: ImageDataRaw = reader.ReadBytes(dataLength); break; } Util.DebugMessage(reader.BaseStream, "Load, End, Channel image, {0}", ID, Layer.Name); Debug.Assert(reader.BaseStream.Position == endPosition, "Pixel data was not fully read in."); } /// /// Decodes the raw image data from the compressed on-disk format into /// an uncompressed bitmap, in native byte order. /// public void DecodeImageData() { if ((ImageCompression == ImageCompression.Raw) && (Layer.PsdFile.BitDepth <= 8)) { ImageData = new NativeArray(ImageDataRaw, Allocator.TempJob); return; } var image = ImageDataFactory.Create(this, ImageDataRaw); var longLength = (long)image.BytesPerRow * Rect.Height; Util.CheckByteArrayLength(longLength); var LocalImageData = new byte[longLength]; image.Read(LocalImageData); ImageData = new NativeArray(LocalImageData, Allocator.TempJob); ImageDataRaw = null; // no longer needed. } } }