diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index e9120aa479..053ec02026 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. @@ -53,6 +54,13 @@ namespace ImageSharp /// internal int MaxHeaderSize { get; private set; } +#if !NETSTANDARD1_1 + /// + /// 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..1ce4f25f4c --- /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. +// + +using System.IO; + +namespace ImageSharp.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 OpenWrite(string path); + } +#endif +} diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..e32fc67338 --- /dev/null +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ImageSharp.IO +{ +#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 OpenWrite(string path) + { + return File.OpenWrite(path); + } + } +#endif +} diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 4b22424da8..0c4aaa7849 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -168,7 +168,7 @@ namespace ImageSharp : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); - using (FileStream fs = File.OpenRead(filePath)) + using (Stream fs = File.OpenRead(filePath)) { this.Load(fs, options); } @@ -349,7 +349,7 @@ namespace ImageSharp public Image Save(Stream stream, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream, options); + this.SaveInternal(stream, this.CurrentImageFormat?.Encoder, options); return this; } @@ -373,9 +373,10 @@ 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); + + this.SaveInternal(stream, format.Encoder, options); + return this; } @@ -405,15 +406,7 @@ namespace ImageSharp /// public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { - 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; - } + this.SaveInternal(stream, encoder, options); return this; } @@ -569,6 +562,29 @@ namespace ImageSharp return target; } + /// + /// Internally saves the image to the given stream using the given image encoder and options. + /// Can be used by overridden by tests to verify save opperations. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the stream or encoder is null. + internal virtual void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + + long startOfStream = stream.Position; + encoder.Encode(this, stream, options); + + // Reset to the start of the stream. + if (stream.CanSeek) + { + stream.Position = startOfStream; + } + } + /// /// Creates a new from this instance /// diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 4f48c13232..043c3d3f1f 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() + { + var configuration = Configuration.CreateDefaultInstance(); + + ImageSharp.IO.IFileSystem fs = configuration.FileSystem; + + Assert.IsType(fs); + } + [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..71fbaa3822 --- /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 (var r = new StreamReader(fs.OpenRead(path))) + { + string data = r.ReadToEnd(); + + Assert.Equal(testData, data); + } + + File.Delete(path); + } + + [Fact] + public void OpenWrite() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + LocalFileSystem fs = new LocalFileSystem(); + + using (var r = new StreamWriter(fs.OpenWrite(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..b6339ce8a9 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -0,0 +1,48 @@ +// +// 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 SaveWatchingImage Image; + private readonly Mock fileSystem; + + public ImageSaveTests() + { + this.fileSystem = new Mock(); + this.Image = new SaveWatchingImage(1, 1, this.fileSystem.Object); + } + + [Fact] + public void SavePath() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.OpenWrite("path")).Returns(stream); + this.Image.Save("path"); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.Equal(this.Image.CurrentImageFormat.Encoder, operation.encoder); + Assert.Null(operation.options); + } + + public void Dispose() + { + this.Image.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs new file mode 100644 index 0000000000..010ad68294 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs @@ -0,0 +1,39 @@ + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using ImageSharp; + using Processing; + using System.Collections.Generic; + using ImageSharp.Formats; + using ImageSharp.IO; + + /// + /// Watches but does not actually run the processors against the image. + /// + /// + public class SaveWatchingImage : Image + { + public List Saves { get; } = new List(); + + public SaveWatchingImage(int width, int height, IFileSystem fs = null) + : base(width, height, Configuration.CreateDefaultInstance()) + { + //switch out the file system for tests + this.Configuration.FileSystem = fs ?? this.Configuration.FileSystem; + } + + internal override void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) + { + + } + + public struct OperationDetails + { + public Stream stream; + public IImageEncoder encoder; + public IEncoderOptions options; + } + } +}