// --------------------------------------------------------------------------------------------------------------------
//
// 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();
}
}
}
}