// -------------------------------------------------------------------------------------------------------------------- // // Copyright © James South and contributors. // Licensed under the Apache License, Version 2.0. // // // Image class which stores the pixels and provides common functionality // such as loading images from files and streams or operation like resizing or cropping. // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor { using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using Formats; /// /// Image class which stores the pixels and provides common functionality /// such as loading images from files and streams or operation like resizing or cropping. /// /// /// The image data is always stored in BGRA format, where the blue, green, red, and /// alpha values are simple bytes. /// [DebuggerDisplay("Image: {Width}x{Height}")] public class Image : ImageBase { /// /// The default horizontal resolution value (dots per inch) in x direction. /// The default value is 96 dots per inch. /// public const double DefaultHorizontalResolution = 96; /// /// The default vertical resolution value (dots per inch) in y direction. /// The default value is 96 dots per inch. /// public const double DefaultVerticalResolution = 96; /// /// The default collection of . /// private static readonly Lazy> DefaultDecoders = new Lazy>(() => new List { new BmpDecoder(), new JpegDecoder(), new PngDecoder(), // new GifDecoder(), }); /// /// The default collection of . /// private static readonly Lazy> DefaultEncoders = new Lazy>(() => new List { new BmpEncoder(), new JpegEncoder(), new PngEncoder() }); /// /// Initializes a new instance of the class. /// public Image() { this.HorizontalResolution = DefaultHorizontalResolution; this.VerticalResolution = DefaultVerticalResolution; } /// /// Initializes a new instance of the class /// with the height and the width of the image. /// /// The width of the image in pixels. /// The height of the image in pixels. public Image(int width, int height) : base(width, height) { this.HorizontalResolution = DefaultHorizontalResolution; this.VerticalResolution = DefaultVerticalResolution; } /// /// Initializes a new instance of the class /// by making a copy from another image. /// /// The other image, where the clone should be made from. /// is null /// (Nothing in Visual Basic). public Image(Image other) : base(other) { Guard.NotNull(other, "other", "Other image cannot be null."); foreach (ImageFrame frame in other.Frames) { if (frame != null) { this.Frames.Add(new ImageFrame(frame)); } } this.HorizontalResolution = DefaultHorizontalResolution; this.VerticalResolution = DefaultVerticalResolution; } /// /// Initializes a new instance of the class. /// /// /// The stream containing image information. /// public Image(Stream stream) { Guard.NotNull(stream, "stream"); this.Load(stream, Decoders); } /// /// Initializes a new instance of the class. /// /// /// The stream containing image information. /// /// /// The collection of . /// public Image(Stream stream, params IImageDecoder[] decoders) { Guard.NotNull(stream, "stream"); this.Load(stream, decoders); } /// /// Gets a list of default decoders. /// public static IList Decoders => DefaultDecoders.Value; /// /// Gets a list of default encoders. /// public static IList Encoders => DefaultEncoders.Value; /// /// Gets or sets the frame delay. /// If not 0, this field specifies the number of hundredths (1/100) of a second to /// wait before continuing with the processing of the Data Stream. /// The clock starts ticking immediately after the graphic is rendered. /// This field may be used in conjunction with the User Input Flag field. /// public int? FrameDelay { get; set; } /// /// Gets or sets the resolution of the image in x- direction. It is defined as /// number of dots per inch and should be an positive value. /// /// The density of the image in x- direction. public double HorizontalResolution { get; set; } /// /// Gets or sets the resolution of the image in y- direction. It is defined as /// number of dots per inch and should be an positive value. /// /// The density of the image in y- direction. public double VerticalResolution { get; set; } /// /// Gets the width of the image in inches. It is calculated as the width of the image /// in pixels multiplied with the density. When the density is equals or less than zero /// the default value is used. /// /// The width of the image in inches. public double InchWidth { get { double resolution = this.HorizontalResolution; if (resolution <= 0) { resolution = DefaultHorizontalResolution; } return this.Width / resolution; } } /// /// Gets the height of the image in inches. It is calculated as the height of the image /// in pixels multiplied with the density. When the density is equals or less than zero /// the default value is used. /// /// The height of the image in inches. public double InchHeight { get { double resolution = this.VerticalResolution; if (resolution <= 0) { resolution = DefaultVerticalResolution; } return this.Height / resolution; } } /// /// Gets a value indicating whether this image is animated. /// /// /// true if this image is animated; otherwise, false. /// public bool IsAnimated => this.Frames.Count > 0; /// /// Gets or sets the number of times any animation is repeated. /// 0 means to repeat indefinitely. /// public ushort RepeatCount { get; set; } /// /// Gets the other frames for the animation. /// /// The list of frame images. public IList Frames { get; } = new List(); /// /// Gets the list of properties for storing meta information about this image. /// /// A list of image properties. public IList Properties { get; } = new List(); /// /// Loads the image from the given stream. /// /// /// The stream containing image information. /// /// /// The collection of . /// /// /// Thrown if the stream is not readable nor seekable. /// private void Load(Stream stream, IList decoders) { try { if (!stream.CanRead) { throw new NotSupportedException("Cannot read from the stream."); } if (!stream.CanSeek) { throw new NotSupportedException("The stream does not support seeking."); } if (decoders.Count > 0) { int maxHeaderSize = decoders.Max(x => x.HeaderSize); if (maxHeaderSize > 0) { byte[] header = new byte[maxHeaderSize]; stream.Read(header, 0, maxHeaderSize); stream.Position = 0; IImageDecoder decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header)); if (decoder != null) { decoder.Decode(this, stream); return; } } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); foreach (IImageDecoder decoder in decoders) { stringBuilder.AppendLine("-" + decoder); } throw new NotSupportedException(stringBuilder.ToString()); } finally { stream.Dispose(); } } } }