diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 2076fab19a..a611119921 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -73,13 +73,14 @@ namespace ImageProcessorCore /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(IImageBase image) - where T : IPackedVector, new() + public IPixelAccessor GetPixelAccessor(IImageBase image) + where T : IPackedVector, new() + where TP : struct { Type packed = typeof(T); if (this.pixelAccessors.ContainsKey(packed)) { - return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); + return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); } throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:"); diff --git a/src/ImageProcessorCore/Common/Helpers/Operator.cs b/src/ImageProcessorCore/Common/Helpers/Operator.cs index fc09a0c7be..8d0d03caae 100644 --- a/src/ImageProcessorCore/Common/Helpers/Operator.cs +++ b/src/ImageProcessorCore/Common/Helpers/Operator.cs @@ -260,6 +260,9 @@ } private static readonly Func add, subtract, multiply, divide; + + private static readonly Func multiplyF, divideF; + /// /// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw /// an InvalidOperationException if the type T does not provide this operator, or for @@ -287,6 +290,10 @@ /// Nullable<TInner> if TInner does not provide this operator. /// public static Func Divide => divide; + + public static Func MultiplyF => multiplyF; + + public static Func DivideF => divideF; } /// @@ -344,6 +351,8 @@ static readonly Func add, subtract, multiply, divide; + static readonly Func multiplyF, divideF; + /// /// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw /// an InvalidOperationException if the type T does not provide this operator, or for @@ -372,8 +381,12 @@ /// public static Func Divide => divide; + public static Func MultiplyF => multiplyF; + + public static Func DivideF => divideF; + static readonly Func equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual; - + /// /// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw /// an InvalidOperationException if the type T does not provide this operator, or for @@ -422,6 +435,8 @@ subtract = ExpressionUtil.CreateExpression(Expression.Subtract); divide = ExpressionUtil.CreateExpression(Expression.Divide); multiply = ExpressionUtil.CreateExpression(Expression.Multiply); + multiplyF = ExpressionUtil.CreateExpression(Expression.Multiply); + divideF = ExpressionUtil.CreateExpression(Expression.Multiply); greaterThan = ExpressionUtil.CreateExpression(Expression.GreaterThan); greaterThanOrEqual = ExpressionUtil.CreateExpression(Expression.GreaterThanOrEqual); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 5565fd690b..916bddce7b 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -74,8 +74,9 @@ namespace ImageProcessorCore.Formats /// /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct { new BmpDecoderCore().Decode(image, stream); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index 56e2d2e2a5..faa0113697 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -59,8 +59,9 @@ namespace ImageProcessorCore.Formats /// - or - /// is null. /// - public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct { this.currentStream = stream; @@ -132,25 +133,19 @@ namespace ImageProcessorCore.Formats if (this.infoHeader.BitsPerPixel == 32) { - this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel == 24) { - this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel == 16) { - this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel <= 8) { - this.ReadRgbPalette( - imageData, - palette, - this.infoHeader.Width, - this.infoHeader.Height, - this.infoHeader.BitsPerPixel, - inverted); + this.ReadRgbPalette(imageData, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted); } break; @@ -199,8 +194,9 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// The number of bits per pixel. /// Whether the bitmap is inverted. - private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - where T : IPackedVector, new() + private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + where T : IPackedVector, new() + where TP : struct { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -259,8 +255,9 @@ namespace ImageProcessorCore.Formats /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb16(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + private void ReadRgb16(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() + where TP : struct { // We divide here as we will store the colors in our floating point format. const int ScaleR = 8; // 256/32 @@ -307,8 +304,9 @@ namespace ImageProcessorCore.Formats /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb24(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + private void ReadRgb24(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() + where TP : struct { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); @@ -345,8 +343,9 @@ namespace ImageProcessorCore.Formats /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb32(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + private void ReadRgb32(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() + where TP : struct { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index f58f6b85f4..07ada9c05a 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -43,8 +43,9 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector, new() + where TP : struct { BmpEncoderCore encoder = new BmpEncoderCore(); encoder.Encode(image, stream, this.BitsPerPixel); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index 1df95592a2..cc26ea700c 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -28,8 +28,9 @@ namespace ImageProcessorCore.Formats /// The to encode from. /// The to encode the image data to. /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where T : IPackedVector, new() + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where T : IPackedVector, new() + where TP : struct { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -127,8 +128,9 @@ namespace ImageProcessorCore.Formats /// /// The containing pixel data. /// - private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where T : IPackedVector, new() + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where T : IPackedVector, new() + where TP : struct { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; @@ -137,7 +139,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (IPixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -159,8 +161,9 @@ namespace ImageProcessorCore.Formats /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where T : IPackedVector, new() + where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) { @@ -185,8 +188,9 @@ namespace ImageProcessorCore.Formats /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where T : IPackedVector, new() + where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) { diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 3c5676bac4..09dbcdb1e7 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -44,7 +44,8 @@ namespace ImageProcessorCore.Formats /// The type of pixels contained within the image. /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) - where T : IPackedVector, new(); + void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 6e83c11ae9..7738968631 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -48,7 +48,8 @@ namespace ImageProcessorCore.Formats /// The type of pixels contained within the image. /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new(); + void Encode(ImageBase image, Stream stream) + where T : IPackedVector, + new() where TP : struct; } } diff --git a/src/ImageProcessorCore/IImageFrame.cs b/src/ImageProcessorCore/IImageFrame.cs deleted file mode 100644 index fc92b9306e..0000000000 --- a/src/ImageProcessorCore/IImageFrame.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ImageProcessorCore -{ - public interface IImageFrame : IImageBase - where T : IPackedVector, new() - { - } -} diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 35eaff2434..3cb8866bbb 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -1,292 +1,22 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// +using System.IO; namespace ImageProcessorCore { - using System.IO; - using System.Text; - - using System; - using System.Collections.Generic; - using System.Linq; - - using Formats; - - /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// - /// - /// The packed vector containing pixel information. - /// - public class Image : ImageBase - where T : IPackedVector, new() + public class Image : Image { - /// - /// 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. - /// - public Image() - { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); - } - - /// - /// 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. + // TODO Constructors. public Image(int width, int height) - : base(width, height) + : base(width, height) { - // TODO: Change to PNG - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); } - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// Thrown if the is null. public Image(Stream stream) + : base(stream) { - Guard.NotNull(stream, nameof(stream)); - this.Load(stream); - } - - /// - /// 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. - public Image(Image other) - { - foreach (ImageFrame frame in other.Frames) - { - if (frame != null) - { - this.Frames.Add(new ImageFrame(frame)); - } - } - - this.RepeatCount = other.RepeatCount; - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.CurrentImageFormat = other.CurrentImageFormat; - } - - /// - /// 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; } - - /// - public override IPixelAccessor Lock() - { - return Bootstrapper.Instance.GetPixelAccessor(this); - } - - /// - /// 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) + public Image(Image other) + : base(other) { - 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/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs similarity index 82% rename from src/ImageProcessorCore/IImageBase.cs rename to src/ImageProcessorCore/Image/IImageBase.cs index 4b78df66c0..256a238f3b 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -2,12 +2,16 @@ namespace ImageProcessorCore { - public interface IImageBase : IImageBase - where T : IPackedVector, new() + public interface IImageBase : IImageBase + where T : IPackedVector, new() + where TP : struct { T[] Pixels { get; } + void ClonePixels(int width, int height, T[] pixels); - IPixelAccessor Lock(); + + IPixelAccessor Lock(); + void SetPixels(int width, int height, T[] pixels); } diff --git a/src/ImageProcessorCore/Image/IImageFrame.cs b/src/ImageProcessorCore/Image/IImageFrame.cs new file mode 100644 index 0000000000..744716fafe --- /dev/null +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -0,0 +1,8 @@ +namespace ImageProcessorCore +{ + public interface IImageFrame : IImageBase + where T : IPackedVector, new() + where TP : struct + { + } +} diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs similarity index 89% rename from src/ImageProcessorCore/IImageProcessor.cs rename to src/ImageProcessorCore/Image/IImageProcessor.cs index 76bc9851c6..6e92c7c8cf 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -45,8 +45,9 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new(); + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where T : IPackedVector, + new() where TP : struct; /// /// Applies the process to the specified portion of the specified at the specified @@ -68,7 +69,8 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new(); + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs new file mode 100644 index 0000000000..890feb9f89 --- /dev/null +++ b/src/ImageProcessorCore/Image/Image.cs @@ -0,0 +1,293 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.IO; + using System.Text; + + using System; + using System.Collections.Generic; + using System.Linq; + + using Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// + /// The packed vector containing pixel information. + /// + public class Image : ImageBase + where T : IPackedVector, new() + where TP : struct + { + /// + /// 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. + /// + public Image() + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); + } + + /// + /// 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) + { + // TODO: Change to PNG + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.Load(stream); + } + + /// + /// 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. + public Image(Image other) + { + foreach (ImageFrame frame in other.Frames) + { + if (frame != null) + { + this.Frames.Add(new ImageFrame(frame)); + } + } + + this.RepeatCount = other.RepeatCount; + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.CurrentImageFormat = other.CurrentImageFormat; + } + + /// + /// 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; } + + /// + public override IPixelAccessor Lock() + { + return Bootstrapper.Instance.GetPixelAccessor(this); + } + + /// + /// 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/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs similarity index 94% rename from src/ImageProcessorCore/ImageBase.cs rename to src/ImageProcessorCore/Image/ImageBase.cs index 357f490010..ec455f271d 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -14,18 +14,19 @@ namespace ImageProcessorCore /// /// The packed vector pixels format. /// - public abstract class ImageBase : IImageBase - where T : IPackedVector, new() + public abstract class ImageBase : IImageBase + where T : IPackedVector, new() + where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected ImageBase() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The width of the image in pixels. /// The height of the image in pixels. @@ -43,15 +44,15 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// /// - /// Thrown if the given is null. + /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -196,6 +197,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs similarity index 84% rename from src/ImageProcessorCore/ImageExtensions.cs rename to src/ImageProcessorCore/Image/ImageExtensions.cs index c30607ce94..c38f951a8d 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -61,8 +61,9 @@ namespace ImageProcessorCore /// The image this method extends. /// The processor to apply to the image. /// The . - public static Image Process(this Image source, IImageProcessor processor) - where T : IPackedVector, new() + public static Image Process(this Image source, IImageProcessor processor) + where T : IPackedVector, new() + where TP : struct { return Process(source, source.Bounds, processor); } @@ -78,8 +79,9 @@ namespace ImageProcessorCore /// /// The processors to apply to the image. /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where T : IPackedVector, new() + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + where T : IPackedVector, new() + where TP : struct { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); } @@ -96,8 +98,9 @@ namespace ImageProcessorCore /// The target image height. /// The processor to apply to the image. /// The . - public static Image Process(this Image source, int width, int height, IImageSampler sampler) - where T : IPackedVector, new() + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + where T : IPackedVector, new() + where TP : struct { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); } @@ -121,8 +124,9 @@ namespace ImageProcessorCore /// /// The processor to apply to the image. /// The . - public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) - where T : IPackedVector, new() + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where T : IPackedVector, new() + where TP : struct { return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } @@ -135,12 +139,13 @@ namespace ImageProcessorCore /// Whether to clone the image. /// The to perform against the image. /// The . - private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) - where T : IPackedVector, new() + private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) + where T : IPackedVector, new() + where TP : struct { - Image transformedImage = clone - ? new Image(source) - : new Image + Image transformedImage = clone + ? new Image(source) + : new Image { // Several properties require copying // TODO: Check why we need to set these? @@ -154,10 +159,10 @@ namespace ImageProcessorCore for (int i = 0; i < source.Frames.Count; i++) { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame tranformedFrame = clone - ? new ImageFrame(sourceFrame) - : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame tranformedFrame = clone + ? new ImageFrame(sourceFrame) + : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; action(sourceFrame, tranformedFrame); diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs similarity index 75% rename from src/ImageProcessorCore/ImageFrame.cs rename to src/ImageProcessorCore/Image/ImageFrame.cs index 790591b182..0846dc7783 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -11,8 +11,9 @@ namespace ImageProcessorCore /// /// The packed vector containing pixel information. /// - public class ImageFrame : ImageBase - where T : IPackedVector, new() + public class ImageFrame : ImageBase + where T : IPackedVector, new() + where TP : struct { /// /// Initializes a new instance of the class. @@ -27,15 +28,15 @@ namespace ImageProcessorCore /// /// The frame to create the frame from. /// - public ImageFrame(ImageFrame frame) + public ImageFrame(ImageFrame frame) : base(frame) { } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { - return Bootstrapper.Instance.GetPixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/ImageProperty.cs b/src/ImageProcessorCore/Image/ImageProperty.cs similarity index 100% rename from src/ImageProcessorCore/ImageProperty.cs rename to src/ImageProcessorCore/Image/ImageProperty.cs diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 380e5c6340..bfa8eacdfc 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -27,8 +27,9 @@ namespace ImageProcessorCore.Processors private int totalRows; /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new() + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct { try { @@ -49,8 +50,9 @@ namespace ImageProcessorCore.Processors } /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where T : IPackedVector, new() + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + where T : IPackedVector, new() + where TP : struct { try { @@ -95,8 +97,9 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct { } @@ -120,8 +123,9 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where T : IPackedVector, new(); + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + where T : IPackedVector, new() + where TP : struct; /// /// This method is called after the process is applied to prepare the processor. @@ -136,8 +140,9 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct { } diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index d1a692e5d5..9c1848aab4 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageProcessorCore { using System; @@ -11,8 +10,19 @@ namespace ImageProcessorCore /// /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// - public struct Bgra32 : IPackedVector, IEquatable + public unsafe struct Bgra32 : IPackedVector, IEquatable { + const uint B_MASK = 0x000000FF; + const uint G_MASK = 0x0000FF00; + const uint R_MASK = 0x00FF0000; + const uint A_MASK = 0xFF000000; + const int B_SHIFT = 0; + const int G_SHIFT = 8; + const int R_SHIFT = 16; + const int A_SHIFT = 24; + + private uint packedValue; + /// /// Initializes a new instance of the struct. /// @@ -23,7 +33,7 @@ namespace ImageProcessorCore public Bgra32(float b, float g, float r, float a) { Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); + this.packedValue = Pack(ref clamped); } /// @@ -35,42 +45,191 @@ namespace ImageProcessorCore public Bgra32(Vector4 vector) { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); + this.packedValue = Pack(ref clamped); + } + + public byte B + { + get + { + return (byte)(this.packedValue & B_MASK); + } + + set + { + this.packedValue = (this.packedValue & ~B_MASK) | value; + } + } + + public byte G + { + get + { + return (byte)((this.packedValue & G_MASK) >> G_SHIFT); + } + + set + { + this.packedValue = (this.packedValue & ~G_MASK) | (((uint)value) << G_SHIFT); + } + } + + public byte R + { + get + { + return (byte)((this.packedValue & R_MASK) >> R_SHIFT); + } + + set + { + this.packedValue = (this.packedValue & ~R_MASK) | (((uint)value) << R_SHIFT); + } + } + + public byte A + { + get + { + return (byte)((this.packedValue & A_MASK) >> A_SHIFT); + } + + set + { + this.packedValue = (this.packedValue & ~A_MASK) | (((uint)value) << A_SHIFT); + } } /// - public uint PackedValue { get; set; } + public uint PackedValue() + { + return this.packedValue; + } + + public void Add(TP value) where TP : IPackedVector + { + // this.PackVector(this.ToVector4() + value.ToVector4()); + } + + public void Subtract(TP value) where TP : IPackedVector + { + // this.PackVector(this.ToVector4() - value.ToVector4()); + } - // The maths are wrong here I just wanted to test the performance to see if the - // issues are caused by the Vector transform or something else. - public void Add(IPackedVector value) + public void Multiply(TP value) where TP : IPackedVector { + // this.PackVector(this.ToVector4() * value.ToVector4()); + } + public void Multiply(float value) where TP : IPackedVector + { + this.B = (byte)(this.B * value); + this.G = (byte)(this.G * value); + this.R = (byte)(this.R * value); + this.A = (byte)(this.A * value); } - public void Subtract(IPackedVector value) + public void Divide(TP value) where TP : IPackedVector { + // this.PackVector(this.ToVector4() / value.ToVector4()); + } + public void Divide(float value) where TP : IPackedVector + { + this.B = (byte)(this.B / value); + this.G = (byte)(this.G / value); + this.R = (byte)(this.R / value); + this.A = (byte)(this.A / value); } - public void Multiply(IPackedVector value) + /// + /// Computes the product of multiplying a Bgra32 by a given factor. + /// + /// The Bgra32. + /// The multiplication factor. + /// + /// The + /// + public static Bgra32 operator *(Bgra32 value, float factor) { + byte b = (byte)(value.B * factor); + byte g = (byte)(value.G * factor); + byte r = (byte)(value.R * factor); + byte a = (byte)(value.A * factor); + return new Bgra32(b, g, r, a); } - public void Multiply(float value) + /// + /// Computes the product of multiplying a Bgra32 by a given factor. + /// + /// The multiplication factor. + /// The Bgra32. + /// + /// The + /// + public static Bgra32 operator *(float factor, Bgra32 value) { + byte b = (byte)(value.B * factor); + byte g = (byte)(value.G * factor); + byte r = (byte)(value.R * factor); + byte a = (byte)(value.A * factor); + return new Bgra32(b, g, r, a); } - public void Divide(IPackedVector value) + /// + /// Computes the product of multiplying two Bgra32s. + /// + /// The Bgra32 on the left hand of the operand. + /// The Bgra32 on the right hand of the operand. + /// + /// The + /// + public static Bgra32 operator *(Bgra32 left, Bgra32 right) + { + byte b = (byte)(left.B * right.B); + byte g = (byte)(left.G * right.G); + byte r = (byte)(left.R * right.R); + byte a = (byte)(left.A * right.A); + + return new Bgra32(b, g, r, a); + } + + /// + /// Computes the sum of adding two Bgra32s. + /// + /// The Bgra32 on the left hand of the operand. + /// The Bgra32 on the right hand of the operand. + /// + /// The + /// + public static Bgra32 operator +(Bgra32 left, Bgra32 right) { + byte b = (byte)(left.B + right.B); + byte g = (byte)(left.G + right.G); + byte r = (byte)(left.R + right.R); + byte a = (byte)(left.A + right.A); + return new Bgra32(b, g, r, a); } - public void Divide(float value) + /// + /// Computes the difference left by subtracting one Bgra32 from another. + /// + /// The Bgra32 on the left hand of the operand. + /// The Bgra32 on the right hand of the operand. + /// + /// The + /// + public static Bgra32 operator -(Bgra32 left, Bgra32 right) { + byte b = (byte)(left.B - right.B); + byte g = (byte)(left.G - right.G); + byte r = (byte)(left.R - right.R); + byte a = (byte)(left.A - right.A); + return new Bgra32(b, g, r, a); } /// @@ -87,7 +246,7 @@ namespace ImageProcessorCore /// public static bool operator ==(Bgra32 left, Bgra32 right) { - return left.PackedValue == right.PackedValue; + return left.packedValue == right.packedValue; } /// @@ -100,31 +259,30 @@ namespace ImageProcessorCore /// public static bool operator !=(Bgra32 left, Bgra32 right) { - return left.PackedValue != right.PackedValue; + return left.packedValue != right.packedValue; } /// public void PackVector(Vector4 vector) { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); + this.packedValue = Pack(ref clamped); } /// public void PackBytes(byte x, byte y, byte z, byte w) { - Vector4 vector = new Vector4(x, y, z, w); - this.PackedValue = Pack(ref vector); + this.packedValue = Pack(ref x, ref y, ref z, ref w); } /// public Vector4 ToVector4() { return new Vector4( - this.PackedValue & 0xFF, - (this.PackedValue >> 8) & 0xFF, - (this.PackedValue >> 16) & 0xFF, - (this.PackedValue >> 24) & 0xFF) / 255f; + this.packedValue & 0xFF, + (this.packedValue >> 8) & 0xFF, + (this.packedValue >> 16) & 0xFF, + (this.packedValue >> 24) & 0xFF) / 255f; } /// @@ -132,11 +290,11 @@ namespace ImageProcessorCore { return new[] { - (byte)(this.PackedValue & 0xFF), - (byte)((this.PackedValue >> 8) & 0xFF), - (byte)((this.PackedValue >> 16) & 0xFF), - (byte)((this.PackedValue >> 24) & 0xFF) - }; + (byte)(this.packedValue & 0xFF), + (byte)((this.packedValue >> 8) & 0xFF), + (byte)((this.packedValue >> 16) & 0xFF), + (byte)((this.packedValue >> 24) & 0xFF) + }; } /// @@ -148,7 +306,7 @@ namespace ImageProcessorCore /// public bool Equals(Bgra32 other) { - return this.PackedValue == other.PackedValue; + return this.packedValue == other.packedValue; } /// @@ -178,9 +336,14 @@ namespace ImageProcessorCore private static uint Pack(ref Vector4 vector) { return (uint)Math.Round(vector.X) | - ((uint)Math.Round(vector.Y) << 8) | - ((uint)Math.Round(vector.Z) << 16) | - ((uint)Math.Round(vector.W) << 24); + ((uint)Math.Round(vector.Y) << 8) | + ((uint)Math.Round(vector.Z) << 16) | + ((uint)Math.Round(vector.W) << 24); + } + + private static uint Pack(ref byte x, ref byte y, ref byte z, ref byte w) + { + return x | ((uint)y << 8) | ((uint)z << 16) | ((uint)w << 24); } /// @@ -194,7 +357,7 @@ namespace ImageProcessorCore /// private int GetHashCode(Bgra32 packed) { - return packed.PackedValue.GetHashCode(); + return packed.packedValue.GetHashCode(); } } } diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 90d52b27f5..2beeb2c809 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -18,12 +18,22 @@ namespace ImageProcessorCore where T : struct { /// - /// Gets or sets the packed representation of the value. + /// Gets the packed representation of the value. /// Typically packed in least to greatest significance order. /// - T PackedValue { get; set; } + T PackedValue(); + void Add(TP value) where TP : IPackedVector; + void Subtract(TP value) where TP : IPackedVector; + + void Multiply(TP value) where TP : IPackedVector; + + void Multiply(float value) where TP : IPackedVector; + + void Divide(TP value) where TP : IPackedVector; + + void Divide(float value) where TP : IPackedVector; } /// @@ -31,30 +41,6 @@ namespace ImageProcessorCore /// public interface IPackedVector { - //void Add(U value); - - //void Subtract(U value); - - //void Multiply(U value); - - //void Multiply(float value); - - //void Divide(U value); - - //void Divide(float value); - - void Add(IPackedVector value); - - void Subtract(IPackedVector value); - - void Multiply(IPackedVector value); - - void Multiply(float value); - - void Divide(IPackedVector value); - - void Divide(float value); - /// /// Sets the packed representation from a . /// diff --git a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index f9fa7005bc..c48090d7b2 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -15,8 +15,7 @@ namespace ImageProcessorCore /// 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 Bgra32PixelAccessor : IPixelAccessor - + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor { /// /// The position of the first pixel in the bitmap. @@ -56,7 +55,7 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); + this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index f726163975..e41e7d0d9b 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,8 +10,9 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IPixelAccessor - where T : IPackedVector, new() + public interface IPixelAccessor : IPixelAccessor + where T : IPackedVector, new() + where TP : struct { /// /// Gets or sets the pixel at the specified position. diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index 0c806d5e86..e99bfbbf0a 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -23,8 +23,9 @@ namespace ImageProcessorCore /// /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { switch (options.Mode) { @@ -54,8 +55,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -173,8 +175,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -254,8 +257,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -341,8 +345,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -382,8 +387,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 3269b00ccd..135d0d62f7 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -9,6 +9,8 @@ namespace ImageProcessorCore.Processors using System.Numerics; using System.Threading.Tasks; + using ImageProcessorCore.Helpers; + /// /// Provides methods that allow the resizing of images using various algorithms. /// @@ -43,7 +45,7 @@ namespace ImageProcessorCore.Processors protected Weights[] VerticalWeights { get; set; } /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { if (!(this.Sampler is NearestNeighborResampler)) { @@ -53,7 +55,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { // Jump out, we'll deal with that later. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) @@ -78,8 +80,8 @@ namespace ImageProcessorCore.Processors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -114,10 +116,10 @@ namespace ImageProcessorCore.Processors // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. - Image firstPass = new Image(target.Width, source.Height); - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor firstPassPixels = firstPass.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + Image firstPass = new Image(target.Width, source.Height); + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor firstPassPixels = firstPass.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, @@ -153,14 +155,13 @@ namespace ImageProcessorCore.Processors //} //firstPassPixels[x, y] = destination; - T sourceColor; T destination = default(T); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - sourceColor = sourcePixels[originX, y]; + T sourceColor = sourcePixels[originX, y]; //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; @@ -168,8 +169,8 @@ namespace ImageProcessorCore.Processors //destination.Add(sourceColor); //destination += sourceColor * xw.Value; - sourceColor.Multiply(xw.Value); - destination.Add(sourceColor); + //sourceColor.Multiply(xw.Value); + destination.Add(Operator.Add(destination, Operator.MultiplyF(sourceColor, xw.Value))); } //if (compand) @@ -200,22 +201,23 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - T sourceColor; T destination = default(T); for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; - sourceColor = firstPassPixels[x, originY]; + T sourceColor = firstPassPixels[x, originY]; //Color sourceColor = compand // ? Color.Expand(firstPassPixels[x, originY]) // : firstPassPixels[x, originY]; //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); //destination += sourceColor * yw.Value; - sourceColor.Multiply(yw.Value); - destination.Add(sourceColor); + //sourceColor.Multiply(yw.Value); + //destination.Add(sourceColor); + destination.Add(Operator.Add(destination, Operator.MultiplyF(sourceColor, yw.Value))); + } //if (compand) @@ -237,7 +239,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 3dd0a9c966..7a08e95bf4 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -21,8 +21,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -50,8 +51,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); } @@ -67,8 +69,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); } @@ -85,8 +88,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); } @@ -110,8 +114,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { if (width == 0 && height > 0) { diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index d900377cda..238a5ccad4 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -23,11 +23,11 @@ [Benchmark(Description = "ImageProcessorCore GetSet Pixel")] public Bgra32 ResizeCore() { - Image image = new Image(400, 400); - using (IPixelAccessor imagePixels = image.Lock()) + Image image = new Image(400, 400); + using (IPixelAccessor imagePixels = image.Lock()) { imagePixels[200, 200] = new Bgra32(1, 1, 1, 1); - return (Bgra32)imagePixels[200, 200]; + return imagePixels[200, 200]; } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index cbc90c7265..65ed841f00 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.Attributes; using CoreSize = ImageProcessorCore.Size; + using CoreImage = ImageProcessorCore.Image; public class Resize { @@ -31,7 +32,7 @@ [Benchmark(Description = "ImageProcessorCore Resize")] public CoreSize ResizeCore() { - Image image = new Image(2000, 2000); + CoreImage image = new CoreImage(2000, 2000); image.Resize(400, 400); return new CoreSize(image.Width, image.Height); }