diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index bdcb4c10f..378b6e8b9 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -21,8 +21,7 @@ namespace SixLabors.ImageSharp.Advanced /// The Pixel format. /// The source image. /// Returns the configuration. - public static Configuration GetConfiguration(this Image source) - where TPixel : struct, IPixel + public static Configuration GetConfiguration(this Image source) => GetConfiguration((IConfigurable)source); /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 82af2a671..4922a9200 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -30,6 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp return new BmpDecoderCore(configuration, this).Decode(stream); } + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 6af75f2d0..6498f4aa2 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -44,5 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Gif var decoder = new GifDecoderCore(configuration, this); return decoder.Identify(stream); } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index ffc40314d..625c4efb6 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -20,5 +20,7 @@ namespace SixLabors.ImageSharp.Formats /// The decoded image Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel; + + Image Decode(Configuration configuration, Stream stream); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 57b70dd26..7459abec5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -38,5 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return decoder.Identify(stream); } } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 39dfb1d0b..e8c5ac8e8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -59,5 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Png var decoder = new PngDecoderCore(configuration, this); return decoder.Identify(stream); } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ No newline at end of file diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 9e83d173f..36944dc2c 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -102,6 +102,18 @@ namespace SixLabors.ImageSharp Image img = decoder.Decode(config, stream); return (img, format); } + + private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) + { + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + if (decoder is null) + { + return (null, null); + } + + Image img = decoder.Decode(config, stream); + return (img, format); + } /// /// Reads the raw image information from the specified stream. diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index b13cef482..80267146d 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path) => Load(path); + public static Image Load(string path) => Load(path); /// /// Create a new instance of the class from the given file. @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, out IImageFormat format) => Load(path, out format); + public static Image Load(string path, out IImageFormat format) => Load(path, out format); /// /// Create a new instance of the class from the given file. @@ -68,19 +68,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(Configuration config, string path) => Load(config, path); - - /// - /// Create a new instance of the class from the given file. - /// - /// The config for the decoder. - /// The file path to the image. - /// The mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) => Load(config, path, out format); + public static Image Load(Configuration config, string path) => Load(config, path); /// /// Create a new instance of the class from the given file. @@ -92,7 +80,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(Configuration config, string path, IImageDecoder decoder) => Load(config, path, decoder); + public static Image Load(Configuration config, string path, IImageDecoder decoder) => Load(config, path, decoder); /// /// Create a new instance of the class from the given file. @@ -103,7 +91,7 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, IImageDecoder decoder) => Load(path, decoder); + public static Image Load(string path, IImageDecoder decoder) => Load(path, decoder); /// /// Create a new instance of the class from the given file. @@ -174,6 +162,14 @@ namespace SixLabors.ImageSharp return Load(config, stream, out format); } } + + public static Image Load(Configuration config, string path, out IImageFormat format) + { + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream, out format); + } + } /// /// Create a new instance of the class from the given file. diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 3236e0007..8848225f5 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp /// the mime type of the decoded image. /// Thrown if the stream is not readable. /// A new .> - public static Image Load(Stream stream, out IImageFormat format) => Load(stream, out format); + public static Image Load(Stream stream, out IImageFormat format) => Load(stream, out format); /// /// Create a new instance of the class from the given stream. @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp /// The stream containing image information. /// Thrown if the stream is not readable. /// A new .> - public static Image Load(Stream stream) => Load(stream); + public static Image Load(Stream stream) => Load(stream); /// /// Create a new instance of the class from the given stream. @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp /// The decoder. /// Thrown if the stream is not readable. /// A new .> - public static Image Load(Stream stream, IImageDecoder decoder) => Load(stream, decoder); + public static Image Load(Stream stream, IImageDecoder decoder) => Load(stream, decoder); /// /// Create a new instance of the class from the given stream. @@ -88,18 +88,7 @@ namespace SixLabors.ImageSharp /// The stream containing image information. /// Thrown if the stream is not readable. /// A new .> - public static Image Load(Configuration config, Stream stream) => Load(config, stream); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The config for the decoder. - /// The stream containing image information. - /// the mime type of the decoded image. - /// Thrown if the stream is not readable. - /// A new .> - public static Image Load(Configuration config, Stream stream, out IImageFormat format) - => Load(config, stream, out format); + public static Image Load(Configuration config, Stream stream) => Load(config, stream); /// /// Create a new instance of the class from the given stream. @@ -193,6 +182,28 @@ namespace SixLabors.ImageSharp throw new NotSupportedException(sb.ToString()); } + private static Image Load(Configuration config, Stream stream, out IImageFormat format) + { + config = config ?? Configuration.Default; + (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + + format = data.format; + + if (data.img != null) + { + return data.img; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + { + sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + } + + throw new NotSupportedException(sb.ToString()); + } private static T WithSeekableStream(Configuration config, Stream stream, Func action) { diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 00a9a2075..a27f16ab3 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.IO; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; @@ -45,5 +47,39 @@ namespace SixLabors.ImageSharp public abstract void Dispose(); internal abstract void AcceptVisitor(IImageVisitor visitor); + + /// + /// 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 or encoder is null. + public void Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + + EncodeVisitor visitor = new EncodeVisitor(encoder, stream); + this.AcceptVisitor(visitor); + } + + class EncodeVisitor : IImageVisitor + { + private readonly IImageEncoder encoder; + + private readonly Stream stream; + + public EncodeVisitor(IImageEncoder encoder, Stream stream) + { + this.encoder = encoder; + this.stream = stream; + } + + public void Visit(Image image) + where TPixel : struct, IPixel + { + this.encoder.Encode(image, this.stream); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index 5010451b8..3bd60694d 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -23,8 +23,7 @@ namespace SixLabors.ImageSharp /// The source image. /// The file path to save the image to. /// Thrown if the stream is null. - public static void Save(this Image source, string filePath) - where TPixel : struct, IPixel + public static void Save(this Image source, string filePath) { Guard.NotNullOrWhiteSpace(filePath, nameof(filePath)); @@ -67,8 +66,7 @@ namespace SixLabors.ImageSharp /// The file path to save the image to. /// The encoder to save the image with. /// Thrown if the encoder is null. - public static void Save(this Image source, string filePath, IImageEncoder encoder) - where TPixel : struct, IPixel + public static void Save(this Image source, string filePath, IImageEncoder encoder) { Guard.NotNull(encoder, nameof(encoder)); using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath)) @@ -85,8 +83,7 @@ namespace SixLabors.ImageSharp /// The stream to save the image to. /// The format to save the image in. /// Thrown if the stream is null. - public static void Save(this Image source, Stream stream, IImageFormat format) - where TPixel : struct, IPixel + public static void Save(this Image source, Stream stream, IImageFormat format) { Guard.NotNull(format, nameof(format)); IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 64ec87c4d..e37f3e4c1 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -158,20 +158,6 @@ namespace SixLabors.ImageSharp set => this.PixelSource.PixelBuffer[x, y] = value; } - /// - /// 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 or encoder is null. - public void Save(Stream stream, IImageEncoder encoder) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - - encoder.Encode(this, stream); - } - /// /// Clones the current image /// diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index e3a43a652..c7af8d468 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -42,6 +42,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + [Fact] + public void Resize_PixelAgnostic() + { + var filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + + using (Image image = Image.Load(filePath)) + { + image.Mutate(x => x.Resize(image.Size() / 2)); + string path = System.IO.Path.Combine( + TestEnvironment.CreateOutputDirectory(nameof(ResizeTests)), + nameof(this.Resize_PixelAgnostic) + ".png"); + + image.Save(path); + } + } + [Theory( Skip = "Debug only, enable manually" )] diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 64357a17e..91e31b356 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -199,6 +199,8 @@ namespace SixLabors.ImageSharp.Tests } public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } public class TestEncoder : ImageSharp.Formats.IImageEncoder diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 3dd330e4d..e81714ddc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -57,5 +57,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return result; } } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 7a775c081..2de3c03aa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -51,5 +51,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 4ef6a582c..b49baa5c4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -379,6 +379,8 @@ namespace SixLabors.ImageSharp.Tests this.callerName = name; invocationCounts[name] = 0; } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } private class TestDecoderWithParameters : IImageDecoder @@ -416,6 +418,8 @@ namespace SixLabors.ImageSharp.Tests this.callerName = name; invocationCounts[name] = 0; } + + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } } \ No newline at end of file