diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index e9120aa479..fa983d3557 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -12,6 +12,7 @@ namespace ImageSharp using System.Threading.Tasks; using Formats; + using ImageSharp.IO; /// /// Provides initialization code which allows extending the library. @@ -33,6 +34,25 @@ namespace ImageSharp /// private readonly List imageFormatsList = new List(); + /// + /// Initializes a new instance of the class. + /// + public Configuration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The inital set of image formats. + public Configuration(params IImageFormat[] providers) + { + foreach (IImageFormat p in providers) + { + this.AddImageFormat(p); + } + } + /// /// Gets the default instance. /// @@ -53,6 +73,13 @@ namespace ImageSharp /// internal int MaxHeaderSize { get; private set; } +#if !NETSTANDARD1_1 + /// + /// Gets or sets the fielsystem helper for accessing the local file system. + /// + internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); +#endif + /// /// Adds a new to the collection of supported image formats. /// diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs new file mode 100644 index 0000000000..ee1ef84d7b --- /dev/null +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System.IO; + + #if !NETSTANDARD1_1 + /// + /// A simple interface representing the filesystem. + /// + public interface IFileSystem + { + /// + /// Returns a readable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream OpenRead(string path); + + /// + /// Creates or opens a file and returns it as a writeable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream Create(string path); + } +#endif +} diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..02a9914ead --- /dev/null +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + + #if !NETSTANDARD1_1 + /// + /// A wrapper around the local File apis. + /// + public class LocalFileSystem : IFileSystem + { + /// + public Stream OpenRead(string path) + { + return File.OpenRead(path); + } + + /// + public Stream Create(string path) + { + return File.Create(path); + } + } +#endif +} diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 4b22424da8..34724cc977 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -168,7 +168,8 @@ namespace ImageSharp : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); - using (FileStream fs = File.OpenRead(filePath)) + + using (Stream fs = this.Configuration.FileSystem.OpenRead(filePath)) { this.Load(fs, options); } @@ -348,9 +349,7 @@ namespace ImageSharp /// The public Image Save(Stream stream, IEncoderOptions options) { - Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream, options); - return this; + return this.Save(stream, this.CurrentImageFormat?.Encoder, options); } /// @@ -373,10 +372,9 @@ namespace ImageSharp /// The public Image Save(Stream stream, IImageFormat format, IEncoderOptions options) { - Guard.NotNull(stream, nameof(stream)); Guard.NotNull(format, nameof(format)); - format.Encoder.Encode(this, stream, options); - return this; + + return this.Save(stream, format.Encoder, options); } /// @@ -407,13 +405,8 @@ namespace ImageSharp { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - encoder.Encode(this, stream, options); - // Reset to the start of the stream. - if (stream.CanSeek) - { - stream.Position = 0; - } + encoder.Encode(this, stream, options); return this; } @@ -446,7 +439,7 @@ namespace ImageSharp throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); } - return this.Save(filePath, format); + return this.Save(filePath, format, options); } /// @@ -472,10 +465,7 @@ namespace ImageSharp public Image Save(string filePath, IImageFormat format, IEncoderOptions options) { Guard.NotNull(format, nameof(format)); - using (FileStream fs = File.Create(filePath)) - { - return this.Save(fs, format); - } + return this.Save(filePath, format.Encoder, options); } /// @@ -501,9 +491,9 @@ namespace ImageSharp public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(encoder, nameof(encoder)); - using (FileStream fs = File.Create(filePath)) + using (Stream fs = this.Configuration.FileSystem.Create(filePath)) { - return this.Save(fs, encoder); + return this.Save(fs, encoder, options); } } #endif diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 4f48c13232..c749239d71 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests using System.Linq; using ImageSharp.Formats; - + using ImageSharp.IO; using Xunit; /// @@ -18,6 +18,16 @@ namespace ImageSharp.Tests /// public class ConfigurationTests { + [Fact] + public void DefaultsToLocalFileSystem() + { + Configuration configuration = Configuration.CreateDefaultInstance(); + + ImageSharp.IO.IFileSystem fs = configuration.FileSystem; + + Assert.IsType(fs); + } + [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs index 3bb3b3e777..2d3d2cc2b8 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests.Drawing.Paths using ImageSharp; using Processing; using System.Collections.Generic; + using ImageSharp.Formats; /// /// Watches but does not actually run the processors against the image. @@ -22,15 +23,11 @@ namespace ImageSharp.Tests.Drawing.Paths public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - ProcessorApplications.Add(new ProcessorDetails + this.ProcessorApplications.Add(new ProcessorDetails { processor = processor, rectangle = rectangle }); - - // doesn't really apply the processor to the fake images as this is supposed - // to be just used to test which processor was finally applied and to interogate - // its settings } public struct ProcessorDetails diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..472d643cd3 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.IO +{ + using System; + using System.IO; + using ImageSharp.IO; + + using Xunit; + + public class LocalFileSystemTests + { + [Fact] + public void OpenRead() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + File.WriteAllText(path, testData); + + LocalFileSystem fs = new LocalFileSystem(); + + using (StreamReader r = new StreamReader(fs.OpenRead(path))) + { + string data = r.ReadToEnd(); + + Assert.Equal(testData, data); + } + + File.Delete(path); + } + + [Fact] + public void Create() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + LocalFileSystem fs = new LocalFileSystem(); + + using (StreamWriter r = new StreamWriter(fs.Create(path))) + { + r.Write(testData); + } + + string data = File.ReadAllText(path); + Assert.Equal(testData, data); + + File.Delete(path); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs new file mode 100644 index 0000000000..0d1c3e09b5 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -0,0 +1,186 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using System.Linq; + using ImageSharp.Formats; + using ImageSharp.IO; + using Moq; + using Xunit; + + /// + /// Tests the class. + /// + public class ImageSaveTests : IDisposable + { + private readonly Image Image; + private readonly Mock fileSystem; + private readonly Mock format; + private readonly Mock formatNotRegistered; + private readonly Mock encoder; + private readonly Mock encoderNotInFormat; + private readonly IEncoderOptions encoderOptions; + + public ImageSaveTests() + { + this.encoder = new Mock(); + this.format = new Mock(); + this.format.Setup(x => x.Encoder).Returns(this.encoder.Object); + this.format.Setup(x => x.Decoder).Returns(new Mock().Object); + this.format.Setup(x => x.MimeType).Returns("img/test"); + this.format.Setup(x => x.Extension).Returns("png"); + this.format.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + + + this.encoderNotInFormat = new Mock(); + this.formatNotRegistered = new Mock(); + this.formatNotRegistered.Setup(x => x.Encoder).Returns(this.encoderNotInFormat.Object); + this.formatNotRegistered.Setup(x => x.Decoder).Returns(new Mock().Object); + this.formatNotRegistered.Setup(x => x.MimeType).Returns("img/test"); + this.formatNotRegistered.Setup(x => x.Extension).Returns("png"); + this.formatNotRegistered.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + + this.fileSystem = new Mock(); + this.encoderOptions = new Mock().Object; + this.Image = new Image(1, 1, new Configuration(this.format.Object) { + FileSystem = this.fileSystem.Object + }); + } + + [Fact] + public void SavePath() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); + this.Image.Save("path.png"); + + this.encoder.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SavePathWithOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderOptions); + + this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SavePathWithEncoder() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SavePathWithEncoderAndOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + + + [Fact] + public void SavePathWithFormat() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SavePathWithFormatAndOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SaveStream() + { + Stream stream = new MemoryStream(); + this.Image.Save(stream); + + this.encoder.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SaveStreamWithOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderOptions); + + this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SaveStreamWithEncoder() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SaveStreamWithEncoderAndOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderNotInFormat.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SaveStreamWithFormat() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.formatNotRegistered.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SaveStreamWithFormatAndOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.formatNotRegistered.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + public void Dispose() + { + this.Image.Dispose(); + } + } +}