From cb2d928ed6c49cc2c23a878a1925f143f5ba6245 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Jul 2016 01:02:45 +1000 Subject: [PATCH] Bring back generic image Former-commit-id: dd3f92b3358e43e8c26e6ad974b8fd1432d3a36d Former-commit-id: e0ce0080191426e2bf6bc73afc0da56bb2545a3c Former-commit-id: ba0189d24efa44c744044a88d05204bc7afecf6e --- src/ImageProcessorCore/GenericImage.cs | 261 ++++++++++++++++++++++++ src/ImageProcessorCore/ImageFrame.cs | 17 +- src/ImageProcessorCore/PixelAccessor.cs | 6 +- 3 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/ImageProcessorCore/GenericImage.cs diff --git a/src/ImageProcessorCore/GenericImage.cs b/src/ImageProcessorCore/GenericImage.cs new file mode 100644 index 000000000..b0408513a --- /dev/null +++ b/src/ImageProcessorCore/GenericImage.cs @@ -0,0 +1,261 @@ + + +namespace ImageProcessorCore +{ + using System.IO; + using System.Text; + + using System; + using System.Collections.Generic; + using System.Linq; + + using ImageProcessorCore.Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// + /// The image data is always stored in RGBA format, where the red, green, blue, and + /// alpha values are floating point numbers. + /// + public abstract class GenericImage : ImageBase + where TPacked : IPackedVector + { + /// + /// 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; + + /// + /// Initializes a new instance of the class. + /// + protected GenericImage() + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// 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. + protected GenericImage(int width, int height) + : base(width, height) + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + protected GenericImage(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.Load(stream); + } + + /// + /// Gets a list of supported image formats. + /// + public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; + + /// + /// 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; } = DefaultHorizontalResolution; + + /// + /// 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; } = DefaultVerticalResolution; + + /// + /// 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(); + + /// + /// Gets the currently loaded image format. + /// + public IImageFormat CurrentImageFormat { get; internal set; } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. + public void Save(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.CurrentImageFormat.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageFormat format) + { + Guard.NotNull(stream, nameof(stream)); + format.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + encoder.Encode(this, stream); + } + + /// + /// Returns a Base64 encoded string from the given image. + /// + /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// The + public override string ToString() + { + using (MemoryStream stream = new MemoryStream()) + { + this.Save(stream); + stream.Flush(); + return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + } + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + private void Load(Stream stream) + { + if (!this.Formats.Any()) { return; } + + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + if (!stream.CanSeek) + { + throw new NotSupportedException("The stream does not support seeking."); + } + + int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize); + if (maxHeaderSize > 0) + { + byte[] header = new byte[maxHeaderSize]; + + stream.Position = 0; + stream.Read(header, 0, maxHeaderSize); + stream.Position = 0; + + IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header)); + if (format != null) + { + format.Decoder.Decode(this, stream); + this.CurrentImageFormat = format; + return; + } + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in this.Formats) + { + stringBuilder.AppendLine("-" + format); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + } +} diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index 42cf08da4..2f1fe518c 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -1,13 +1,26 @@ namespace ImageProcessorCore { - public class ImageFrame : GenericImageFrame + /// + /// Represents a single frame in a animation. + /// + /// + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. + /// + public class ImageFrame : ImageBase, IImageFrame { + /// + /// Initializes a new instance of the class. + /// + /// + /// The frame to create the frame from. + /// public ImageFrame(ImageFrame frame) : base(frame) { } - /// + /// public override IPixelAccessor Lock() { return new PixelAccessor(this); diff --git a/src/ImageProcessorCore/PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor.cs index bf01cb856..360649132 100644 --- a/src/ImageProcessorCore/PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor.cs @@ -9,8 +9,12 @@ namespace ImageProcessorCore using System.Runtime.InteropServices; /// - /// Provides per-pixel access to an images pixels as 8 bit unsigned components. + /// Provides per-pixel access to an images pixels. /// + /// + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. + /// public sealed unsafe class PixelAccessor : IPixelAccessor { ///