/// <summary> /// Reads the blending ranges of a layer from a stream and store them in a PSD layer object. /// </summary> /// <param name="layer">The layer whose mask is being read.</param> /// <param name="stream">The stream from which to read the layer mask.</param> public static void ReadLayerBlendingRanges(PSDLayer layer, Stream stream) { int length = stream.ReadBigEndianInt32(); if (length % 8 != 0) { throw new PSDFormatException($"expected a layer blending ranges block length divisible by 8, got {length} ({length % 8} remainder)"); } int count = length / 8; var blendingRanges = new PSDLayerBlendingRange[count]; for (int i = 0; i < count; ++i) { blendingRanges[i].SourceLowFirst = stream.ReadByteOrThrow(); blendingRanges[i].SourceLowSecond = stream.ReadByteOrThrow(); blendingRanges[i].SourceHighFirst = stream.ReadByteOrThrow(); blendingRanges[i].SourceHighSecond = stream.ReadByteOrThrow(); blendingRanges[i].DestinationLowFirst = stream.ReadByteOrThrow(); blendingRanges[i].DestinationLowSecond = stream.ReadByteOrThrow(); blendingRanges[i].DestinationHighFirst = stream.ReadByteOrThrow(); blendingRanges[i].DestinationHighSecond = stream.ReadByteOrThrow(); } layer.BlendingRanges = blendingRanges; }
/// <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 information of a PSD layer's mask from a stream. /// </summary> /// <param name="layer">The layer whose mask is being read.</param> /// <param name="stream">The stream from which to read the layer mask.</param> public static void ReadLayerMask(PSDLayer layer, Stream stream) { int maskSize = stream.ReadBigEndianInt32(); if (maskSize == 0) { layer.LayerMask = null; return; } var mask = new PSDLayerMask(); mask.Top = stream.ReadBigEndianInt32(); mask.Left = stream.ReadBigEndianInt32(); mask.Bottom = stream.ReadBigEndianInt32(); mask.Right = stream.ReadBigEndianInt32(); byte defaultColor = stream.ReadByteOrThrow(); if (defaultColor != 0 && defaultColor != 255) { throw new PSDFormatException($"expected layer mask default color to be 0 or 255, got {defaultColor}"); } mask.DefaultColor = defaultColor; byte flags = stream.ReadByteOrThrow(); if ((flags & 0x01) == 0x01) { mask.PositionIsRelativeToLayerData = true; } if ((flags & 0x02) == 0x02) { mask.Disabled = true; } if ((flags & 0x04) == 0x04) { mask.InvertMaskWhenBlending = true; } if ((flags & 0x08) == 0x08) { mask.OriginatesFromRenderingOtherData = true; } bool hasParameters = ((flags & 0x10) == 0x10); if (maskSize == 20 && !hasParameters) { // skip two bytes of padding and we're done stream.ReadBytes(2); return; } // rectangle + default color + flags + real flags + real BG + real rectangle int actualMaskSize = 4 * 4 + 1 + 1 + 1 + 1 + 4 * 4; if (hasParameters) { actualMaskSize += 1; // read parameters byte availableParameters = stream.ReadByteOrThrow(); if ((availableParameters & 0x01) == 0x01) { actualMaskSize += 1; mask.UserMaskDensity = stream.ReadByteOrThrow(); } if ((availableParameters & 0x02) == 0x02) { actualMaskSize += 8; mask.UserMaskFeather = stream.ReadBigEndianDouble(); } if ((availableParameters & 0x04) == 0x04) { actualMaskSize += 1; mask.VectorMaskDensity = stream.ReadByteOrThrow(); } if ((availableParameters & 0x08) == 0x08) { actualMaskSize += 8; mask.VectorMaskFeather = stream.ReadBigEndianDouble(); } } if (actualMaskSize != maskSize) { throw new PSDFormatException( $"actual calculated layer mask size ({actualMaskSize}) does not match mask size in header ({maskSize})" ); } // yay, duplicated data... // FIXME: let's just skip it stream.ReadBytes(18); }
/// <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); } }