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;
+ }
+ }
+}