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