/// <summary> /// Reads a layer record from a stream and populates a PSD layer object with the information. /// </summary> /// <param name="psd"> /// The PSD file being read. Used to obtain the file format version and to populate the /// <see cref="PSDFile.Layers"/> array if general layer information has been "outsourced" to an additional /// layer information block. /// </param> /// <param name="layer">The layer to populate with information read from the stream.</param> /// <param name="stream">The stream from which to read layer information.</param> public static void ReadLayerRecord(PSDFile psd, PSDLayer layer, Stream stream) { layer.Top = stream.ReadBigEndianInt32(); layer.Left = stream.ReadBigEndianInt32(); layer.Bottom = stream.ReadBigEndianInt32(); layer.Right = stream.ReadBigEndianInt32(); short numberChannels = stream.ReadBigEndianInt16(); layer.Channels = new PSDLayerChannel[numberChannels]; for (int i = 0; i < numberChannels; ++i) { var channel = new PSDLayerChannel(); channel.ID = stream.ReadBigEndianInt16(); channel.DataLength = (psd.Version == 2) ? stream.ReadBigEndianInt64() : stream.ReadBigEndianInt32(); layer.Channels[i] = channel; } string blendModeSignature = stream.ReadUsAsciiString(4); if (blendModeSignature != BlendModeSignature) { throw new PSDFormatException($"expected blend mode signature \"{BlendModeSignature}\", got \"{blendModeSignature}\""); } int blendModeValue = stream.ReadBigEndianInt32(); if (!Enum.IsDefined(typeof(BlendMode), blendModeValue)) { char[] blendModeChars = { (char)((blendModeValue >> 24) & 0xFF), (char)((blendModeValue >> 16) & 0xFF), (char)((blendModeValue >> 8) & 0xFF), (char)((blendModeValue >> 0) & 0xFF) }; string blendModeString = new string(blendModeChars); throw new PSDFormatException($"invalid blend mode {blendModeValue} (\"{blendModeString}\")"); } layer.BlendMode = (BlendMode)blendModeValue; byte opacity = stream.ReadByteOrThrow(); layer.Opacity = opacity; byte clipping = stream.ReadByteOrThrow(); if (clipping > 1) { throw new PSDFormatException($"invalid clipping value {clipping}; expected 0 (base) or 1 (non-base)"); } layer.NonBaseClipping = (clipping == 1); byte flags = stream.ReadByteOrThrow(); if ((flags & 0x01) != 0) { layer.TransparencyProtected = true; } if ((flags & 0x02) != 0) { layer.Visible = true; } if ((flags & 0x04) != 0) { layer.Obsolete = true; } if ((flags & 0x18) == 0x18) { // (both bits 3 and 4 must be set) layer.PixelDataIrrelevantToDocumentAppearance = true; } // filler stream.ReadByteOrThrow(); int extraDataLength = stream.ReadBigEndianInt32(); long extraDataStart = stream.Position; ReadLayerMask(layer, stream); ReadLayerBlendingRanges(layer, stream); byte layerNameLength = stream.ReadByteOrThrow(); layer.Name = stream.ReadWindows1252String(layerNameLength); int padTo4Bytes = 4 - ((layerNameLength + 1) % 4); if (padTo4Bytes != 4) // there is zero padding if == 4 { stream.ReadBytes(padTo4Bytes); } layer.AdditionalInformation = new List <PSDAdditionalLayerInformation>(); while (stream.Position < extraDataStart + extraDataLength) { PSDAdditionalLayerInformation pali = PSDFile.Reading.ReadAdditionalLayerInformation(psd, stream); layer.AdditionalInformation.Add(pali); } }
/// <summary> /// Reads the layer information subsection of a PSD layer and mask information section from a stream and /// populates a <see cref="PSDFile"/> with the information. /// </summary> /// <param name="psd">The PSD file object to populate.</param> /// <param name="stream">The stream from which to read the layer information subsection.</param> /// <param name="roundToFourBytes"> /// Whether the layer information block is padded to a multiple of 4 bytes. /// </param> public static void ReadLayerInformation(PSDFile psd, Stream stream, bool roundToFourBytes = false) { long layerInfoLength = (psd.Version == 2) ? stream.ReadBigEndianInt64() : stream.ReadBigEndianInt32(); if (layerInfoLength == 0) { // no layers at all psd.Layers = new PSDLayer[0]; return; } long layerInfoStart = stream.Position; short layerCount = stream.ReadBigEndianInt16(); if (layerCount < 0) { // FIXME: "If it is a negative number, its absolute value is the number of layers and the first alpha channel contains the transparency data for the merged result." layerCount = (short)-layerCount; } psd.Layers = new PSDLayer[layerCount]; for (int i = 0; i < layerCount; ++i) { psd.Layers[i] = new PSDLayer(); PSDLayer.Reading.ReadLayerRecord(psd, psd.Layers[i], stream); } long totalDataLength = 0; for (int l = 0; l < layerCount; ++l) { PSDLayer layer = psd.Layers[l]; for (int c = 0; c < layer.Channels.Length; ++c) { PSDLayerChannel channel = layer.Channels[c]; // get compression type short compressionValue = stream.ReadBigEndianInt16(); if (!Enum.IsDefined(typeof(CompressionType), compressionValue)) { throw new PSDFormatException($"unknown layer data compression type {compressionValue}"); } var compression = (CompressionType)compressionValue; channel.Data = new PSDLayerChannelDataPlaceholder { Compression = compression, Offset = stream.Position, DataLength = channel.DataLength - 2 }; long dataLengthWithCompressionInfo = channel.DataLength; totalDataLength += dataLengthWithCompressionInfo; // skip stream.Seek(channel.Data.DataLength, SeekOrigin.Current); } } // skip padding if any if (stream.Position < layerInfoStart + layerInfoLength) { // padding; skip int skipBytes = (int)((layerInfoStart + layerInfoLength) - stream.Position); stream.ReadBytes(skipBytes); } // additionally round up to 4 bytes if requested (part of Layr/Lr16/Lr32) // WARNING: spec is a liar; says "rounded up to an even byte count" if (roundToFourBytes && layerInfoLength % 4 != 0) { var skipCount = (int)(4 - (layerInfoLength % 4)); stream.ReadBytes(skipCount); } }