예제 #1
0
파일: VersionInfo.cs 프로젝트: radtek/PSD
        /// <summary>
        /// Reads version information from a PSD file object.
        /// </summary>
        /// <param name="psd">The PSD file object from which to obtain version information.</param>
        /// <returns>
        /// The version information obtained from the PSD file, or <c>null</c> if the file contains no version
        /// information.
        /// </returns>
        public static VersionInfo FromPSD(PSDFile psd)
        {
            var resource = psd.ImageResources.FirstOrDefault(ir => ir.ID == VersionInfoResourceID);

            if (resource == null)
            {
                return(null);
            }

            var verInfo = new VersionInfo();

            using (var ms = new MemoryStream(resource.Data, writable: false))
            {
                verInfo.Version = ms.ReadBigEndianInt32();

                byte hasRealMergedData = ms.ReadByteOrThrow();
                if (hasRealMergedData > 1)
                {
                    throw new PSDFormatException($"got hasRealMergedData value {hasRealMergedData}, expected 0 or 1");
                }
                verInfo.HasValidPrecomposedData = (hasRealMergedData == 1);

                verInfo.WriterName  = ms.ReadUnicodeString();
                verInfo.ReaderName  = ms.ReadUnicodeString();
                verInfo.FileVersion = ms.ReadBigEndianInt32();
            }

            return(verInfo);
        }
예제 #2
0
        /// <summary>
        /// Obtains the precomposed image data of a PSD image by decoding rows of PackBits-encoded data from a source
        /// stream and writing the decoded data to a target stream.
        /// </summary>
        /// <remarks><paramref name="source"/> must already be at the correct position.</remarks>
        /// <param name="source">The stream from which to copy the data.</param>
        /// <param name="dest">The stream to which to copy the data.</param>
        /// <param name="psd">The PSD file from which to obtain the number of PackBits-encoded rows.</param>
        /// <param name="cancelToken">
        /// A cancellation token which allows copying to be cancelled before it completes.
        /// </param>
        public static void DecodePackBits(Stream source, Stream dest, PSDFile psd,
                                          CancellationToken cancelToken = default(CancellationToken))
        {
            int scanlineCount = psd.Height * psd.NumberOfChannels;

            DecodePackBits(source, dest, scanlineCount, psd.Version == 2, cancelToken);
        }
예제 #3
0
            /// <summary>
            /// Reads metadata about a PSD file's precomposed image data and populates a <see cref="PSDFile"/> with it.
            /// </summary>
            /// <param name="psd">The PSD file object to populate.</param>
            /// <param name="stream">The stream from which to read the precomposed image data metadata.</param>
            public static void CreateImageDataPlaceholder(PSDFile psd, Stream stream)
            {
                var placeholder = new PSDImageDataPlaceholder();

                // read compression method
                short compressionValue = stream.ReadBigEndianInt16();

                if (!Enum.IsDefined(typeof(CompressionType), compressionValue))
                {
                    throw new PSDFormatException($"unexpected compression type {compressionValue}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<CompressionType, short>())}");
                }
                placeholder.Compression = (CompressionType)compressionValue;
                placeholder.Offset      = stream.Position;

                psd.PrecomposedImageData = placeholder;
            }
예제 #4
0
            /// <summary>
            /// Reads PSD color mode data 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 PSD color mode data.</param>
            public static void ReadColorModeData(PSDFile psd, Stream stream)
            {
                int colorModeDataLength = stream.ReadBigEndianInt32();

                if (colorModeDataLength < 0)
                {
                    throw new PSDFormatException($"color mode data length is {colorModeDataLength}, expected at least 0");
                }

                bool requiresColorModeData = (psd.ColorMode == ColorMode.Indexed || psd.ColorMode == ColorMode.Duotone);
                bool hasColorModeData      = (colorModeDataLength > 0);

                if (hasColorModeData != requiresColorModeData)
                {
                    throw new PSDFormatException($"color mode is {psd.ColorMode} but color mode data length is 0");
                }

                psd.ColorModeData = stream.ReadBytes(colorModeDataLength);
            }
예제 #5
0
            /// <summary>
            /// Reads the global mask 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>
            public static void ReadGlobalMaskInformation(PSDFile psd, Stream stream)
            {
                int length = stream.ReadBigEndianInt32();

                if (length == 0)
                {
                    // no global mask info
                    psd.GlobalLayerMask = null;
                    return;
                }

                var mask = new PSDGlobalLayerMask();

                mask.OverlayColorSpace = stream.ReadBigEndianInt16();
                mask.ColorComponent1   = stream.ReadBigEndianInt16();
                mask.ColorComponent2   = stream.ReadBigEndianInt16();
                mask.ColorComponent3   = stream.ReadBigEndianInt16();
                mask.ColorComponent4   = stream.ReadBigEndianInt16();

                short opacity = stream.ReadBigEndianInt16();

                if (opacity < 0 || opacity > 100)
                {
                    throw new PSDFormatException($"global mask opacity must be at least 0 and at most 100, got {opacity}");
                }
                mask.Opacity = opacity;

                byte kind = stream.ReadByteOrThrow();

                if (!Enum.IsDefined(typeof(LayerMaskKind), kind))
                {
                    throw new PSDFormatException($"unknown layer mask kind {kind}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<LayerMaskKind, byte>())}");
                }
                mask.Kind = (LayerMaskKind)kind;

                int paddingByteCount = length - 13;

                stream.ReadBytes(paddingByteCount);

                psd.GlobalLayerMask = mask;
            }
예제 #6
0
            /// <summary>
            /// Reads PSD layer and mask information 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 PSD layer and mask information.</param>
            public static void ReadLayerAndMaskInformation(PSDFile psd, Stream stream)
            {
                long layerMaskInfoLength = (psd.Version == 2)
                    ? stream.ReadBigEndianInt64()
                    : stream.ReadBigEndianInt32();

                long layerStartPosition = stream.Position;

                ReadLayerInformation(psd, stream);
                ReadGlobalMaskInformation(psd, stream);

                psd.AdditionalLayerInformation = new List <PSDAdditionalLayerInformation>();
                while (stream.Position - layerStartPosition < layerMaskInfoLength)
                {
                    var additionalLayerInfo = ReadAdditionalLayerInformation(psd, stream);
                    if (additionalLayerInfo != null)
                    {
                        psd.AdditionalLayerInformation.Add(additionalLayerInfo);
                    }
                }
            }
예제 #7
0
            /// <summary>
            /// Reads the PSD image resources 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 PSD image resources.</param>
            public static void ReadImageResources(PSDFile psd, Stream stream)
            {
                int imageResourceSectionLength = stream.ReadBigEndianInt32();

                if (imageResourceSectionLength < 0)
                {
                    throw new PSDFormatException(
                              $"image resource section length is {imageResourceSectionLength}, expected at least 0");
                }

                psd.ImageResources = new List <PSDImageResource>();

                long endPosition = stream.Position + imageResourceSectionLength;

                while (stream.Position < endPosition)
                {
                    var resource = new PSDImageResource();
                    PSDImageResource.Reading.ReadResourceBlock(resource, stream);
                    psd.ImageResources.Add(resource);
                }
            }
예제 #8
0
            /// <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);
                }
            }
예제 #9
0
        /// <summary>
        /// Reads resolution information from a PSD file object.
        /// </summary>
        /// <param name="psd">The PSD file object from which to obtain resolution information.</param>
        /// <returns>
        /// The resolution information obtained from the PSD file, or <c>null</c> if the file contains no resolution
        /// information.
        /// </returns>
        public static ResolutionInfo FromPSD(PSDFile psd)
        {
            var resource = psd.ImageResources.FirstOrDefault(ir => ir.ID == ResolutionInfoResourceID);

            if (resource == null)
            {
                return(null);
            }

            var resInfo = new ResolutionInfo();

            using (var ms = new MemoryStream(resource.Data, writable: false))
            {
                int horizontalResolutionDPIFixedPoint = ms.ReadBigEndianInt32();
                resInfo.HorizontalResolutionDPI = horizontalResolutionDPIFixedPoint / FixedPointDivisor;
                if (resInfo.HorizontalResolutionDPI <= 0.0)
                {
                    throw new PSDFormatException($"horizontal resolution is {resInfo.HorizontalResolutionDPI}, expected more than 0.0");
                }

                resInfo.HorizontalResolutionDisplayUnit = (ResolutionDisplayUnit)ms.ReadBigEndianInt16();
                if (!Enum.IsDefined(typeof(ResolutionDisplayUnit), resInfo.HorizontalResolutionDisplayUnit))
                {
                    throw new PSDFormatException(
                              $"horizontal resolution display unit is {resInfo.HorizontalResolutionDisplayUnit}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<ResolutionDisplayUnit, short>())}"
                              );
                }

                resInfo.WidthDisplayUnit = (SizeDisplayUnit)ms.ReadBigEndianInt16();
                if (!Enum.IsDefined(typeof(SizeDisplayUnit), resInfo.WidthDisplayUnit))
                {
                    throw new PSDFormatException(
                              $"width display unit is {resInfo.WidthDisplayUnit}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<SizeDisplayUnit, short>())}"
                              );
                }

                int verticalResolutionDPIFixedPoint = ms.ReadBigEndianInt32();
                resInfo.VerticalResolutionDPI = verticalResolutionDPIFixedPoint / FixedPointDivisor;
                if (resInfo.VerticalResolutionDPI <= 0.0)
                {
                    throw new PSDFormatException($"vertical resolution is {resInfo.VerticalResolutionDPI}, expected more than 0.0");
                }

                resInfo.VerticalResolutionDisplayUnit = (ResolutionDisplayUnit)ms.ReadBigEndianInt16();
                if (!Enum.IsDefined(typeof(ResolutionDisplayUnit), resInfo.VerticalResolutionDisplayUnit))
                {
                    throw new PSDFormatException(
                              $"vertical resolution display unit is {resInfo.VerticalResolutionDisplayUnit}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<ResolutionDisplayUnit, short>())}"
                              );
                }

                resInfo.HeightDisplayUnit = (SizeDisplayUnit)ms.ReadBigEndianInt16();
                if (!Enum.IsDefined(typeof(SizeDisplayUnit), resInfo.HeightDisplayUnit))
                {
                    throw new PSDFormatException(
                              $"height display unit is {resInfo.HeightDisplayUnit}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<SizeDisplayUnit, short>())}"
                              );
                }
            }

            return(resInfo);
        }
예제 #10
0
            /// <summary>
            /// Reads an item of additional layer information within the additional layer information subsection of a
            /// PSD layer and mask information section from a stream and either returns a
            /// <see cref="PSDAdditionalLayerInformation"/> object containing the additional information, or (if this
            /// is an "out-of-band" layer information block) populates a <see cref="PSDFile"/> with the layer
            /// information within.
            /// </summary>
            /// <param name="psd">
            /// The PSD file object to populate if this is an "out-of-band" layer information block.
            /// </param>
            /// <param name="stream">The stream from which to read the layer information subsection.</param>
            /// <returns>
            /// The additional layer information, or <c>null</c> if the encountered block is an "out-of-band" layer
            /// information block.
            /// </returns>
            public static PSDAdditionalLayerInformation ReadAdditionalLayerInformation(PSDFile psd, Stream stream)
            {
                var ali = new PSDAdditionalLayerInformation();

                string signature = stream.ReadUsAsciiString(4);

                if (signature != AdditionalLayerInfoSignature && signature != AdditionalLayerInfoSignature2)
                {
                    throw new PSDFormatException($"unknown additional layer information signature \"{signature}\", expected \"{AdditionalLayerInfoSignature}\" or \"{AdditionalLayerInfoSignature2}\"");
                }

                string key         = stream.ReadUsAsciiString(4);
                bool   length64Bit = false;

                if (key == "Layr" || key == "Lr16" || key == "Lr32")
                {
                    // layer info has been shunted in here instead of where it belongs
                    ReadLayerInformation(psd, stream, roundToFourBytes: true);
                    return(null);
                }

                if (psd.Version == 2)
                {
                    // some keys have 64-bit lengths
                    length64Bit =
                        (key == "LMsk") ||
                        (key == "Mt16") ||
                        (key == "Mt32") ||
                        (key == "Mtrn") ||
                        (key == "Alph") ||
                        (key == "FMsk") ||
                        (key == "Ink2") ||
                        (key == "FEid") ||
                        (key == "FXid") ||
                        (key == "PxSD")
                    ;
                }

                long length = length64Bit
                    ? stream.ReadBigEndianInt64()
                    : stream.ReadBigEndianInt32();

                byte[] data = stream.ReadBytes((int)length);

                ali.Key  = key;
                ali.Data = data;

                // round up to 4 bytes
                // WARNING: spec is a liar; says "rounded up to an even byte count"
                if (length % 4 != 0)
                {
                    var skipCount = (int)(4 - (length % 4));
                    stream.ReadBytes(skipCount);
                }

                return(ali);
            }
예제 #11
0
            /// <summary>
            /// Reads the PSD header 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 PSD header.</param>
            public static void ReadHeader(PSDFile psd, Stream stream)
            {
                string magic = stream.ReadUsAsciiString(4);

                if (!string.Equals(magic, Magic, StringComparison.Ordinal))
                {
                    throw new PSDFormatException($"unexpected magic value (is \"{magic}\", expected \"{Magic}\")");
                }

                short version = stream.ReadBigEndianInt16();

                if (version < 1 || version > 2)
                {
                    throw new PSDFormatException($"version is {version}, expected 1 or 2");
                }
                psd.Version = version;

                byte[] reserved = stream.ReadBytes(6);
                if (reserved.Any(b => b != 0x00))
                {
                    throw new PSDFormatException("nonzero byte in reserved 6-byte value");
                }

                short numberOfChannels = stream.ReadBigEndianInt16();

                if (numberOfChannels < MinChannels || numberOfChannels > MaxChannels)
                {
                    throw new PSDFormatException(
                              $"number of channels is is {numberOfChannels}, expected at least {MinChannels} and at most {MaxChannels}");
                }
                psd.NumberOfChannels = numberOfChannels;

                int height = stream.ReadBigEndianInt32();

                if (height < 1)
                {
                    throw new PSDFormatException($"height is {height}, expected at least 1");
                }
                if (version == 1 && height > Version1MaxDimension)
                {
                    throw new PSDFormatException(
                              $"height is {height} and this is a version 1 document, expected at most {Version1MaxDimension}");
                }
                if (version == 2 && height > Version2MaxDimension)
                {
                    throw new PSDFormatException(
                              $"height is {height} and this is a version 2 document, expected at most {Version2MaxDimension}");
                }
                psd.Height = height;

                int width = stream.ReadBigEndianInt32();

                if (width < 1)
                {
                    throw new PSDFormatException($"width is {width}, expected at least 1");
                }
                if (version == 1 && width > Version1MaxDimension)
                {
                    throw new PSDFormatException(
                              $"width is {width} and this is a version 1 document, expected at most {Version1MaxDimension}");
                }
                if (version == 2 && width > Version2MaxDimension)
                {
                    throw new PSDFormatException(
                              $"width is {width} and this is a version 2 document, expected at most {Version2MaxDimension}");
                }
                psd.Width = width;

                short depth = stream.ReadBigEndianInt16();

                if (depth != 1 && depth != 8 && depth != 16 && depth != 32)
                {
                    throw new PSDFormatException($"depth is {depth}, expected 1 or 8 or 16 or 32");
                }
                psd.Depth = depth;

                short colorMode = stream.ReadBigEndianInt16();

                if (!Enum.IsDefined(typeof(ColorMode), colorMode))
                {
                    throw new PSDFormatException(
                              $"color mode is {colorMode}, expected one of {string.Join(", ", EnumUtils.GetUnderlyingValues<ColorMode, short>())}");
                }
                psd.ColorMode = (ColorMode)colorMode;
            }
예제 #12
0
            /// <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);
                }
            }
예제 #13
0
 /// <summary>
 /// Obtains the precomposed image data of a PSD image by decoding per-row delta-encoded and then
 /// Deflate-compressed data from a source stream and writing the decoded data to a target stream.
 /// </summary>
 /// <remarks><paramref name="source"/> must already be at the correct position.</remarks>
 /// <param name="source">The stream from which to copy the data.</param>
 /// <param name="dest">The stream to which to copy the data.</param>
 /// <param name="psd">The PSD file from which to obtain the image depth and width.</param>
 /// <param name="cancelToken">
 /// A cancellation token which allows copying to be cancelled before it completes.
 /// </param>
 public static void DecodeZipPredicted(Stream source, Stream dest, PSDFile psd,
                                       CancellationToken cancelToken = default(CancellationToken))
 {
     DecodeZipPredicted(source, dest, null, psd.Depth, psd.Width, cancelToken);
 }