Browse Source

Merge branch 'master' into webp

pull/1552/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
40ea7b4c9c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .github/FUNDING.yml
  2. 14
      .github/workflows/build-and-test.yml
  3. 111
      README.md
  4. 8
      src/Directory.Build.props
  5. 4
      src/Directory.Build.targets
  6. 4
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  7. 12
      src/ImageSharp/Common/Helpers/Guard.cs
  8. 581
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  9. 69
      src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs
  10. 5
      src/ImageSharp/Image.FromStream.cs
  11. 82
      src/ImageSharp/Image.WrapMemory.cs
  12. 63
      src/ImageSharp/ImageExtensions.cs
  13. 57
      src/ImageSharp/Memory/ByteMemoryManager{T}.cs
  14. 3
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  15. 31
      src/ImageSharp/Memory/MemoryOwnerExtensions.cs
  16. 6
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
  17. 2
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  18. 16
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  19. 117
      tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs
  20. 379
      tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs
  21. 42
      tests/ImageSharp.Tests/IO/FixedCapacityPooledMemoryStreamTests.cs
  22. 48
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  23. 15
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs
  24. 31
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  25. 164
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  26. 43
      tests/ImageSharp.Tests/Image/NonSeekableStream.cs
  27. 53
      tests/ImageSharp.Tests/Image/NoneSeekableStream.cs
  28. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  29. 13
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  30. 24
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  31. 2
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

3
.github/FUNDING.yml

@ -1 +1,2 @@
open_collective: imagesharp
github: SixLabors
open_collective: sixlabors

14
.github/workflows/build-and-test.yml

@ -9,7 +9,6 @@ on:
pull_request:
branches:
- master
jobs:
Build:
strategy:
@ -37,6 +36,7 @@ jobs:
codecov: false
runs-on: ${{matrix.options.os}}
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
@ -52,11 +52,6 @@ jobs:
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Setup DotNet SDK
uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- name: Build
shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}"
@ -69,7 +64,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Update Codecov
uses: codecov/codecov-action@v1.0.7
uses: codecov/codecov-action@v1
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests
@ -95,11 +90,6 @@ jobs:
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Setup DotNet SDK
uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- name: Pack
shell: pwsh
run: ./ci-pack.ps1

111
README.md

@ -1,11 +1,10 @@
<h1 align="center">
<h1 align="center">
<img src="https://github.com/SixLabors/Branding/raw/master/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/>
<br/>
SixLabors.ImageSharp
</h1>
<div align="center">
[![Build Status](https://img.shields.io/github/workflow/status/SixLabors/ImageSharp/Build/master)](https://github.com/SixLabors/ImageSharp/actions)
@ -50,7 +49,7 @@ Install stable releases via Nuget; development releases are available via MyGet.
| Package Name | Release (NuGet) | Nightly (MyGet) |
|--------------------------------|-----------------|-----------------|
| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) |
| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/vpre/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) |
## Manual build
@ -95,72 +94,40 @@ Please... Spread the word, contribute algorithms, submit performance improvement
- [Scott Williams](https://github.com/tocsoft)
- [Brian Popow](https://github.com/brianpopow)
### Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/imagesharp#backer)]
<a href="https://opencollective.com/imagesharp/backer/0/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/1/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/2/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/3/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/4/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/5/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/6/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/7/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/8/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/9/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/10/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/11/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/12/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/13/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/14/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/15/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/16/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/17/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/18/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/19/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/20/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/21/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/22/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/23/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/24/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/25/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/26/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/27/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/28/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/29/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/29/avatar.svg"></a>
### Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/imagesharp#sponsor)]
<a href="https://opencollective.com/imagesharp/sponsor/0/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/1/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/2/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/3/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/4/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/5/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/6/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/7/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/8/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/9/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/10/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/11/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/12/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/13/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/14/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/15/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/16/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/17/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/18/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/19/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/20/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/21/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/22/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/23/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/24/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/25/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/26/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/27/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/28/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/29/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/29/avatar.svg"></a>
## Sponsor Six Labors
Support the efforts of the development of the Six Labors projects. [[Become a sponsor :heart:](https://opencollective.com/sixlabors#sponsor)]
### Platinum Sponsors
Become a platinum sponsor with a monthly donation of $2000 (providing 32 hours of maintenance and development) and get 2 hours of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (large) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/avatar.svg?avatarHeight=192"></a>
### Gold Sponsors
Become a gold sponsor with a monthly donation of $1000 (providing 16 hours of maintenance and development) and get 1 hour of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (medium) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/avatar.svg?avatarHeight=156"></a>
### Silver Sponsors
Become a silver sponsor with a monthly donation of $500 (providing 8 hours of maintenance and development) and get your logo (medium) on our README on GitHub and the product pages of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/avatar.svg?avatarHeight=128"></a>
### Bronze Sponsors
Become a bronze sponsor with a monthly donation of $100 and get your logo (small) on our README on GitHub.
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/avatar.svg?avatarHeight=96"></a>

8
src/Directory.Build.props

@ -41,10 +41,10 @@
<ItemGroup>
<!-- DynamicProxyGenAssembly2 is needed so Moq can use our internals -->
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" PublicKey="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
<InternalsVisibleTo Include="ImageSharp.Benchmarks" PublicKey="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" PublicKey="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" PublicKey="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
<InternalsVisibleTo Include="ImageSharp.Benchmarks" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" Key="$(SixLaborsPublicKey)" />
</ItemGroup>

4
src/Directory.Build.targets

@ -46,10 +46,10 @@
Condition="'$(Language)' == 'VB' or '$(Language)' == 'C#'"
Inputs="$(MSBuildAllProjects)"
Outputs="$(GeneratedInternalsVisibleToFile)">
<CreateItem Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute" AdditionalMetadata="_Parameter1=%(InternalsVisibleTo.Identity)" Condition="'%(InternalsVisibleTo.PublicKey)' == ''">
<CreateItem Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute" AdditionalMetadata="_Parameter1=%(InternalsVisibleTo.Identity)" Condition="'%(InternalsVisibleTo.Key)' == ''">
<Output TaskParameter="Include" ItemName="InternalsVisibleToAttribute" />
</CreateItem>
<CreateItem Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute" AdditionalMetadata="_Parameter1=%(InternalsVisibleTo.Identity), PublicKey=%(InternalsVisibleTo.PublicKey)" Condition="'%(InternalsVisibleTo.PublicKey)' != ''">
<CreateItem Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute" AdditionalMetadata="_Parameter1=%(InternalsVisibleTo.Identity), PublicKey=%(InternalsVisibleTo.Key)" Condition="'%(InternalsVisibleTo.Key)' != ''">
<Output TaskParameter="Include" ItemName="InternalsVisibleToAttribute" />
</CreateItem>

4
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -23,7 +23,9 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="filePath">The target file path to save the image to.</param>
/// <returns>The matching encoder.</returns>
/// <exception cref="ArgumentNullException">The file path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
/// <returns>The matching <see cref="IImageEncoder"/>.</returns>
public static IImageEncoder DetectEncoder(this Image source, string filePath)
{
Guard.NotNull(filePath, nameof(filePath));

12
src/ImageSharp/Common/Helpers/Guard.cs

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
#if NETSTANDARD1_3
using System.Reflection;
#endif
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp;
@ -20,10 +22,16 @@ namespace SixLabors
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeValueType<TValue>(TValue value, string parameterName)
{
if (!value.GetType().GetTypeInfo().IsValueType)
if (value.GetType()
#if NETSTANDARD1_3
.GetTypeInfo()
#endif
.IsValueType)
{
ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName);
return;
}
ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName);
}
}
}

581
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -0,0 +1,581 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.IO
{
/// <summary>
/// Provides an in-memory stream composed of non-contiguous chunks that doesn't need to be resized.
/// Chunks are allocated by the <see cref="MemoryAllocator"/> assigned via the constructor
/// and is designed to take advantage of buffer pooling when available.
/// </summary>
internal sealed class ChunkedMemoryStream : Stream
{
/// <summary>
/// The default length in bytes of each buffer chunk.
/// </summary>
public const int DefaultBufferLength = 128 * 1024;
// The memory allocator.
private readonly MemoryAllocator allocator;
// Data
private MemoryChunk memoryChunk;
// The length of each buffer chunk
private readonly int chunkLength;
// Has the stream been disposed.
private bool isDisposed;
// Current chunk to write to
private MemoryChunk writeChunk;
// Offset into chunk to write to
private int writeOffset;
// Current chunk to read from
private MemoryChunk readChunk;
// Offset into chunk to read from
private int readOffset;
/// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
public ChunkedMemoryStream(MemoryAllocator allocator)
: this(DefaultBufferLength, allocator)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
/// <param name="bufferLength">The length, in bytes of each buffer chunk.</param>
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(int bufferLength, MemoryAllocator allocator)
{
Guard.MustBeGreaterThan(bufferLength, 0, nameof(bufferLength));
Guard.NotNull(allocator, nameof(allocator));
this.chunkLength = bufferLength;
this.allocator = allocator;
}
/// <inheritdoc/>
public override bool CanRead => !this.isDisposed;
/// <inheritdoc/>
public override bool CanSeek => !this.isDisposed;
/// <inheritdoc/>
public override bool CanWrite => !this.isDisposed;
/// <inheritdoc/>
public override long Length
{
get
{
this.EnsureNotDisposed();
int length = 0;
MemoryChunk chunk = this.memoryChunk;
while (chunk != null)
{
MemoryChunk next = chunk.Next;
if (next != null)
{
length += chunk.Length;
}
else
{
length += this.writeOffset;
}
chunk = next;
}
return length;
}
}
/// <inheritdoc/>
public override long Position
{
get
{
this.EnsureNotDisposed();
if (this.readChunk is null)
{
return 0;
}
int pos = 0;
MemoryChunk chunk = this.memoryChunk;
while (chunk != this.readChunk)
{
pos += chunk.Length;
chunk = chunk.Next;
}
pos += this.readOffset;
return pos;
}
set
{
this.EnsureNotDisposed();
if (value < 0)
{
ThrowArgumentOutOfRange(nameof(value));
}
// Back up current position in case new position is out of range
MemoryChunk backupReadChunk = this.readChunk;
int backupReadOffset = this.readOffset;
this.readChunk = null;
this.readOffset = 0;
int leftUntilAtPos = (int)value;
MemoryChunk chunk = this.memoryChunk;
while (chunk != null)
{
if ((leftUntilAtPos < chunk.Length)
|| ((leftUntilAtPos == chunk.Length)
&& (chunk.Next is null)))
{
// The desired position is in this chunk
this.readChunk = chunk;
this.readOffset = leftUntilAtPos;
break;
}
leftUntilAtPos -= chunk.Length;
chunk = chunk.Next;
}
if (this.readChunk is null)
{
// Position is out of range
this.readChunk = backupReadChunk;
this.readOffset = backupReadOffset;
ThrowArgumentOutOfRange(nameof(value));
}
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override long Seek(long offset, SeekOrigin origin)
{
this.EnsureNotDisposed();
switch (origin)
{
case SeekOrigin.Begin:
this.Position = offset;
break;
case SeekOrigin.Current:
this.Position += offset;
break;
case SeekOrigin.End:
this.Position = this.Length + offset;
break;
}
return this.Position;
}
/// <inheritdoc/>
public override void SetLength(long value)
=> throw new NotSupportedException();
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
try
{
this.isDisposed = true;
if (disposing)
{
this.ReleaseMemoryChunks(this.memoryChunk);
}
this.memoryChunk = null;
this.writeChunk = null;
this.readChunk = null;
}
finally
{
base.Dispose(disposing);
}
}
/// <inheritdoc/>
public override void Flush()
{
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(byte[] buffer, int offset, int count)
{
Guard.NotNull(buffer, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset));
Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count));
const string BufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), BufferMessage);
return this.ReadImpl(buffer.AsSpan().Slice(offset, count));
}
#if SUPPORTS_SPAN_STREAM
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(Span<byte> buffer) => this.ReadImpl(buffer);
#endif
private int ReadImpl(Span<byte> buffer)
{
this.EnsureNotDisposed();
if (this.readChunk is null)
{
if (this.memoryChunk is null)
{
return 0;
}
this.readChunk = this.memoryChunk;
this.readOffset = 0;
}
Span<byte> chunkBuffer = this.readChunk.Buffer.GetSpan();
int chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
int bytesRead = 0;
int offset = 0;
int count = buffer.Length;
while (count > 0)
{
if (this.readOffset == chunkSize)
{
// Exit if no more chunks are currently available
if (this.readChunk.Next is null)
{
break;
}
this.readChunk = this.readChunk.Next;
this.readOffset = 0;
chunkBuffer = this.readChunk.Buffer.GetSpan();
chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
}
int readCount = Math.Min(count, chunkSize - this.readOffset);
chunkBuffer.Slice(this.readOffset, readCount).CopyTo(buffer.Slice(offset));
offset += readCount;
count -= readCount;
this.readOffset += readCount;
bytesRead += readCount;
}
return bytesRead;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int ReadByte()
{
this.EnsureNotDisposed();
if (this.readChunk is null)
{
if (this.memoryChunk is null)
{
return 0;
}
this.readChunk = this.memoryChunk;
this.readOffset = 0;
}
byte[] chunkBuffer = this.readChunk.Buffer.Array;
int chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
if (this.readOffset == chunkSize)
{
// Exit if no more chunks are currently available
if (this.readChunk.Next is null)
{
return -1;
}
this.readChunk = this.readChunk.Next;
this.readOffset = 0;
chunkBuffer = this.readChunk.Buffer.Array;
}
return chunkBuffer[this.readOffset++];
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Write(byte[] buffer, int offset, int count)
{
Guard.NotNull(buffer, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset));
Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count));
const string BufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), BufferMessage);
this.WriteImpl(buffer.AsSpan().Slice(offset, count));
}
#if SUPPORTS_SPAN_STREAM
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Write(ReadOnlySpan<byte> buffer) => this.WriteImpl(buffer);
#endif
private void WriteImpl(ReadOnlySpan<byte> buffer)
{
this.EnsureNotDisposed();
if (this.memoryChunk is null)
{
this.memoryChunk = this.AllocateMemoryChunk();
this.writeChunk = this.memoryChunk;
this.writeOffset = 0;
}
Span<byte> chunkBuffer = this.writeChunk.Buffer.GetSpan();
int chunkSize = this.writeChunk.Length;
int count = buffer.Length;
int offset = 0;
while (count > 0)
{
if (this.writeOffset == chunkSize)
{
// Allocate a new chunk if the current one is full
this.writeChunk.Next = this.AllocateMemoryChunk();
this.writeChunk = this.writeChunk.Next;
this.writeOffset = 0;
chunkBuffer = this.writeChunk.Buffer.GetSpan();
chunkSize = this.writeChunk.Length;
}
int copyCount = Math.Min(count, chunkSize - this.writeOffset);
buffer.Slice(offset, copyCount).CopyTo(chunkBuffer.Slice(this.writeOffset));
offset += copyCount;
count -= copyCount;
this.writeOffset += copyCount;
}
}
/// <inheritdoc/>
public override void WriteByte(byte value)
{
this.EnsureNotDisposed();
if (this.memoryChunk is null)
{
this.memoryChunk = this.AllocateMemoryChunk();
this.writeChunk = this.memoryChunk;
this.writeOffset = 0;
}
byte[] chunkBuffer = this.writeChunk.Buffer.Array;
int chunkSize = this.writeChunk.Length;
if (this.writeOffset == chunkSize)
{
// Allocate a new chunk if the current one is full
this.writeChunk.Next = this.AllocateMemoryChunk();
this.writeChunk = this.writeChunk.Next;
this.writeOffset = 0;
chunkBuffer = this.writeChunk.Buffer.Array;
}
chunkBuffer[this.writeOffset++] = value;
}
/// <summary>
/// Copy entire buffer into an array.
/// </summary>
/// <returns>The <see cref="T:byte[]"/>.</returns>
public byte[] ToArray()
{
int length = (int)this.Length; // This will throw if stream is closed
byte[] copy = new byte[this.Length];
MemoryChunk backupReadChunk = this.readChunk;
int backupReadOffset = this.readOffset;
this.readChunk = this.memoryChunk;
this.readOffset = 0;
this.Read(copy, 0, length);
this.readChunk = backupReadChunk;
this.readOffset = backupReadOffset;
return copy;
}
/// <summary>
/// Write remainder of this stream to another stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public void WriteTo(Stream stream)
{
this.EnsureNotDisposed();
Guard.NotNull(stream, nameof(stream));
if (this.readChunk is null)
{
if (this.memoryChunk is null)
{
return;
}
this.readChunk = this.memoryChunk;
this.readOffset = 0;
}
byte[] chunkBuffer = this.readChunk.Buffer.Array;
int chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
// Following code mirrors Read() logic (readChunk/readOffset should
// point just past last byte of last chunk when done)
// loop until end of chunks is found
while (true)
{
if (this.readOffset == chunkSize)
{
// Exit if no more chunks are currently available
if (this.readChunk.Next is null)
{
break;
}
this.readChunk = this.readChunk.Next;
this.readOffset = 0;
chunkBuffer = this.readChunk.Buffer.Array;
chunkSize = this.readChunk.Length;
if (this.readChunk.Next is null)
{
chunkSize = this.writeOffset;
}
}
int writeCount = chunkSize - this.readOffset;
stream.Write(chunkBuffer, this.readOffset, writeCount);
this.readOffset = chunkSize;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureNotDisposed()
{
if (this.isDisposed)
{
ThrowDisposed();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowDisposed()
=> throw new ObjectDisposedException(null, "The stream is closed.");
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRange(string value)
=> throw new ArgumentOutOfRangeException(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private MemoryChunk AllocateMemoryChunk()
{
IManagedByteBuffer buffer = this.allocator.AllocateManagedByteBuffer(this.chunkLength);
return new MemoryChunk
{
Buffer = buffer,
Next = null,
Length = buffer.Length()
};
}
private void ReleaseMemoryChunks(MemoryChunk chunk)
{
while (chunk != null)
{
chunk.Dispose();
chunk = chunk.Next;
}
}
private sealed class MemoryChunk : IDisposable
{
private bool isDisposed;
public IManagedByteBuffer Buffer { get; set; }
public MemoryChunk Next { get; set; }
public int Length { get; set; }
private void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this.Buffer.Dispose();
}
this.Buffer = null;
this.isDisposed = true;
}
}
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
}

69
src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs

@ -1,69 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.IO
{
/// <summary>
/// A memory stream constructed from a pooled buffer of known length.
/// </summary>
internal sealed class FixedCapacityPooledMemoryStream : MemoryStream
{
private readonly IManagedByteBuffer buffer;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="FixedCapacityPooledMemoryStream"/> class.
/// </summary>
/// <param name="length">The length of the stream buffer to rent.</param>
/// <param name="allocator">The allocator to rent the buffer from.</param>
public FixedCapacityPooledMemoryStream(long length, MemoryAllocator allocator)
: this(RentBuffer(length, allocator)) => this.Length = length;
private FixedCapacityPooledMemoryStream(IManagedByteBuffer buffer)
: base(buffer.Array) => this.buffer = buffer;
/// <inheritdoc/>
public override long Length { get; }
/// <inheritdoc/>
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
{
if (this.isDisposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
buffer = new ArraySegment<byte>(this.buffer.Array, 0, this.buffer.Length());
return true;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!this.isDisposed)
{
this.isDisposed = true;
if (disposing)
{
this.buffer.Dispose();
}
base.Dispose(disposing);
}
}
// In the extrememly unlikely event someone ever gives us a stream
// with length longer than int.MaxValue then we'll use something else.
private static IManagedByteBuffer RentBuffer(long length, MemoryAllocator allocator)
{
Guard.MustBeBetweenOrEqualTo(length, 0, int.MaxValue, nameof(length));
return allocator.AllocateManagedByteBuffer((int)length);
}
}
}

5
src/ImageSharp/Image.FromStream.cs

@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -731,7 +732,7 @@ namespace SixLabors.ImageSharp
}
// We want to be able to load images from things like HttpContext.Request.Body
using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator);
stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize);
memoryStream.Position = 0;
@ -775,7 +776,7 @@ namespace SixLabors.ImageSharp
return await action(stream, cancellationToken).ConfigureAwait(false);
}
using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;

82
src/ImageSharp/Image.WrapMemory.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp
{
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
@ -38,6 +38,7 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
Guard.IsTrue(pixelMemory.Length == width * height, nameof(pixelMemory), "The length of the input memory doesn't match the specified image size");
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory);
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
@ -64,7 +65,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
@ -81,7 +82,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
/// It will be disposed together with the result image.
@ -105,6 +106,7 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
Guard.IsTrue(pixelMemoryOwner.Memory.Length == width * height, nameof(pixelMemoryOwner), "The length of the input memory doesn't match the specified image size");
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
@ -112,7 +114,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
/// It will be disposed together with the result image.
@ -134,7 +136,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
/// It will be disposed together with the result image.
@ -150,5 +152,73 @@ namespace SixLabors.ImageSharp
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The metadata is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
Memory<byte> byteMemory,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
var memoryManager = new ByteMemoryManager<TPixel>(byteMemory);
Guard.IsTrue(memoryManager.Memory.Length == width * height, nameof(byteMemory), "The length of the input memory doesn't match the specified image size");
var memorySource = MemoryGroup<TPixel>.Wrap(memoryManager.Memory);
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
}
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
Memory<byte> byteMemory,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(configuration, byteMemory, width, height, new ImageMetadata());
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Memory<byte> byteMemory,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(Configuration.Default, byteMemory, width, height);
}
}

63
src/ImageSharp/ImageExtensions.cs

@ -18,27 +18,29 @@ namespace SixLabors.ImageSharp
public static partial class ImageExtensions
{
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// Writes the image to the given file path using an encoder detected from the path.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
public static void Save(this Image source, string path)
=> source.Save(path, source.DetectEncoder(path));
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// Writes the image to the given file path using an encoder detected from the path.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
=> source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// Writes the image to the given file path using the given image encoder.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
@ -56,7 +58,7 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// Writes the image to the given file path using the given image encoder.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
@ -73,12 +75,15 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder));
using Stream fs = source.GetConfiguration().FileSystem.Create(path);
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
{
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// Writes the image to the given stream using the given image format.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -115,6 +120,50 @@ namespace SixLabors.ImageSharp
source.Save(stream, encoder);
}
/// <summary>
/// Writes the image to the given stream using the given image format.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image in.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The format is null.</exception>
/// <exception cref="NotSupportedException">The stream is not writable.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided format.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsync(
this Image source,
Stream stream,
IImageFormat format,
CancellationToken cancellationToken = default)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(format, nameof(format));
if (!stream.CanWrite)
{
throw new NotSupportedException("Cannot write to the stream.");
}
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null)
{
var sb = new StringBuilder();
sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new NotSupportedException(sb.ToString());
}
return source.SaveAsync(stream, encoder, cancellationToken);
}
/// <summary>
/// Returns a Base64 encoded string from the given image.
/// The result is prepended with a Data URI <see href="https://en.wikipedia.org/wiki/Data_URI_scheme"/>

57
src/ImageSharp/Memory/ByteMemoryManager{T}.cs

@ -0,0 +1,57 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that can wrap <see cref="Memory{T}"/> of <see cref="byte"/> instances
/// and cast them to be <see cref="Memory{T}"/> for any arbitrary unmanaged <typeparamref name="T"/> value type.
/// </summary>
/// <typeparam name="T">The value type to use when casting the wrapped <see cref="Memory{T}"/> instance.</typeparam>
internal sealed class ByteMemoryManager<T> : MemoryManager<T>
where T : unmanaged
{
/// <summary>
/// The wrapped <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>
private readonly Memory<byte> memory;
/// <summary>
/// Initializes a new instance of the <see cref="ByteMemoryManager{T}"/> class.
/// </summary>
/// <param name="memory">The <see cref="Memory{T}"/> of <see cref="byte"/> instance to wrap.</param>
public ByteMemoryManager(Memory<byte> memory)
{
this.memory = memory;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public override Span<T> GetSpan()
{
return MemoryMarshal.Cast<byte, T>(this.memory.Span);
}
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
// We need to adjust the offset into the wrapped byte segment,
// as the input index refers to the target-cast memory of T.
// We just have to shift this index by the byte size of T.
return this.memory.Slice(elementIndex * Unsafe.SizeOf<T>()).Pin();
}
/// <inheritdoc/>
public override void Unpin()
{
}
}
}

3
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -100,8 +100,5 @@ namespace SixLabors.ImageSharp.Memory
AllocationOptions options = AllocationOptions.None)
where T : struct
=> MemoryGroup<T>.Allocate(memoryAllocator, totalLength, bufferAlignment, options);
internal static MemoryStream AllocateFixedCapacityMemoryStream(this MemoryAllocator allocator, long length) =>
new FixedCapacityPooledMemoryStream(length, allocator);
}
}

31
src/ImageSharp/Memory/MemoryOwnerExtensions.cs

@ -13,12 +13,27 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
internal static class MemoryOwnerExtensions
{
/// <summary>
/// Gets a <see cref="Span{T}"/> from an <see cref="IMemoryOwner{T}"/> instance.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer)
=> buffer.Memory.Span;
{
return buffer.Memory.Span;
}
/// <summary>
/// Gets the length of an <see cref="IMemoryOwner{T}"/> internal buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <returns>The length of the buffer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Length<T>(this IMemoryOwner<T> buffer)
=> buffer.GetSpan().Length;
{
return buffer.Memory.Length;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offsetted position inside the buffer.
@ -55,8 +70,16 @@ namespace SixLabors.ImageSharp.Memory
buffer.GetSpan().Clear();
}
/// <summary>
/// Gets a reference to the first item in the internal buffer for an <see cref="IMemoryOwner{T}"/> instance.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <returns>A reference to the first item within the memory wrapped by <paramref name="buffer"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference<T>(this IMemoryOwner<T> buffer)
where T : struct =>
ref MemoryMarshal.GetReference(buffer.GetSpan());
where T : struct
{
return ref MemoryMarshal.GetReference(buffer.GetSpan());
}
}
}

6
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs

@ -29,8 +29,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="BokehBlurProcessor"/> class.
/// </summary>
public BokehBlurProcessor()
: this(DefaultRadius, DefaultComponents, DefaultGamma)
{
this.Radius = DefaultRadius;
this.Components = DefaultComponents;
this.Gamma = DefaultGamma;
}
/// <summary>
@ -47,6 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </param>
public BokehBlurProcessor(int radius, int components, float gamma)
{
Guard.MustBeGreaterThan(radius, 0, nameof(radius));
Guard.MustBeBetweenOrEqualTo(components, 1, 6, nameof(components));
Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma));
this.Radius = radius;

2
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -262,7 +262,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.source = source;
this.bounds = bounds;
this.scale = processor.DitherScale;
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length);
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Length);
}
[MethodImpl(InliningOptions.ShortMethod)]

16
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -147,6 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
var operation = new NNRowOperation(
sourceRectangle,
destinationRectangle,
interest,
widthFactor,
heightFactor,
source,
@ -197,6 +198,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
private readonly Rectangle sourceBounds;
private readonly Rectangle destinationBounds;
private readonly Rectangle interest;
private readonly float widthFactor;
private readonly float heightFactor;
private readonly ImageFrame<TPixel> source;
@ -206,6 +208,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public NNRowOperation(
Rectangle sourceBounds,
Rectangle destinationBounds,
Rectangle interest,
float widthFactor,
float heightFactor,
ImageFrame<TPixel> source,
@ -213,6 +216,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
this.sourceBounds = sourceBounds;
this.destinationBounds = destinationBounds;
this.interest = interest;
this.widthFactor = widthFactor;
this.heightFactor = heightFactor;
this.source = source;
@ -224,19 +228,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
int sourceX = this.sourceBounds.X;
int sourceY = this.sourceBounds.Y;
int destX = this.destinationBounds.X;
int destY = this.destinationBounds.Y;
int destLeft = this.destinationBounds.Left;
int destRight = this.destinationBounds.Right;
int destOriginX = this.destinationBounds.X;
int destOriginY = this.destinationBounds.Y;
int destLeft = this.interest.Left;
int destRight = this.interest.Right;
// Y coordinates of source points
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY));
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY));
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y);
for (int x = destLeft; x < destRight; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)];
targetRow[x] = sourceRow[(int)(((x - destOriginX) * this.widthFactor) + sourceX)];
}
}
}

117
tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs

@ -21,8 +21,12 @@ namespace SixLabors.ImageSharp.Benchmarks.IO
private MemoryStream stream4;
private MemoryStream stream5;
private MemoryStream stream6;
private ChunkedMemoryStream chunkedMemoryStream1;
private ChunkedMemoryStream chunkedMemoryStream2;
private BufferedReadStream bufferedStream1;
private BufferedReadStream bufferedStream2;
private BufferedReadStream bufferedStream3;
private BufferedReadStream bufferedStream4;
private BufferedReadStreamWrapper bufferedStreamWrap1;
private BufferedReadStreamWrapper bufferedStreamWrap2;
@ -35,8 +39,20 @@ namespace SixLabors.ImageSharp.Benchmarks.IO
this.stream4 = new MemoryStream(this.buffer);
this.stream5 = new MemoryStream(this.buffer);
this.stream6 = new MemoryStream(this.buffer);
this.stream6 = new MemoryStream(this.buffer);
this.chunkedMemoryStream1 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator);
this.chunkedMemoryStream1.Write(this.buffer);
this.chunkedMemoryStream1.Position = 0;
this.chunkedMemoryStream2 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator);
this.chunkedMemoryStream2.Write(this.buffer);
this.chunkedMemoryStream2.Position = 0;
this.bufferedStream1 = new BufferedReadStream(Configuration.Default, this.stream3);
this.bufferedStream2 = new BufferedReadStream(Configuration.Default, this.stream4);
this.bufferedStream3 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream1);
this.bufferedStream4 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream2);
this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5);
this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6);
}
@ -46,8 +62,12 @@ namespace SixLabors.ImageSharp.Benchmarks.IO
{
this.bufferedStream1?.Dispose();
this.bufferedStream2?.Dispose();
this.bufferedStream3?.Dispose();
this.bufferedStream4?.Dispose();
this.bufferedStreamWrap1?.Dispose();
this.bufferedStreamWrap2?.Dispose();
this.chunkedMemoryStream1?.Dispose();
this.chunkedMemoryStream2?.Dispose();
this.stream1?.Dispose();
this.stream2?.Dispose();
this.stream3?.Dispose();
@ -86,6 +106,21 @@ namespace SixLabors.ImageSharp.Benchmarks.IO
return r;
}
[Benchmark]
public int BufferedReadStreamChunkedRead()
{
int r = 0;
BufferedReadStream reader = this.bufferedStream3;
byte[] b = this.chunk2;
for (int i = 0; i < reader.Length / 2; i++)
{
r += reader.Read(b, 0, 2);
}
return r;
}
[Benchmark]
public int BufferedReadStreamWrapRead()
{
@ -129,6 +164,20 @@ namespace SixLabors.ImageSharp.Benchmarks.IO
return r;
}
[Benchmark]
public int BufferedReadStreamChunkedReadByte()
{
int r = 0;
BufferedReadStream reader = this.bufferedStream4;
for (int i = 0; i < reader.Length; i++)
{
r += reader.ReadByte();
}
return r;
}
[Benchmark]
public int BufferedReadStreamWrapReadByte()
{
@ -167,40 +216,46 @@ namespace SixLabors.ImageSharp.Benchmarks.IO
}
/*
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19041
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.301
[Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT
Job-LKLBOT : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT
Job-RSTMKF : .NET Core 2.1.19 (CoreCLR 4.6.28928.01, CoreFX 4.6.28928.04), X64 RyuJIT
Job-PZIHIV : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT
.NET Core SDK=3.1.401
[Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
Job-OKZLUV : .NET Framework 4.8 (4.8.4084.0), X64 RyuJIT
Job-CPYMXV : .NET Core 2.1.21 (CoreCLR 4.6.29130.01, CoreFX 4.6.29130.02), X64 RyuJIT
Job-BSGVGU : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
IterationCount=3 LaunchCount=1 WarmupCount=3
| Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------------- |-------------- |----------:|------------:|-----------:|------:|--------:|------:|------:|------:|----------:|
| StandardStreamRead | .NET 4.7.2 | 63.238 us | 49.7827 us | 2.7288 us | 0.66 | 0.13 | - | - | - | - |
| BufferedReadStreamRead | .NET 4.7.2 | 66.092 us | 0.4273 us | 0.0234 us | 0.69 | 0.11 | - | - | - | - |
| BufferedReadStreamWrapRead | .NET 4.7.2 | 26.216 us | 3.0527 us | 0.1673 us | 0.27 | 0.04 | - | - | - | - |
| StandardStreamReadByte | .NET 4.7.2 | 97.900 us | 261.7204 us | 14.3458 us | 1.00 | 0.00 | - | - | - | - |
| BufferedReadStreamReadByte | .NET 4.7.2 | 97.260 us | 1.2979 us | 0.0711 us | 1.01 | 0.15 | - | - | - | - |
| BufferedReadStreamWrapReadByte | .NET 4.7.2 | 19.170 us | 2.2296 us | 0.1222 us | 0.20 | 0.03 | - | - | - | - |
| ArrayReadByte | .NET 4.7.2 | 12.878 us | 11.1292 us | 0.6100 us | 0.13 | 0.02 | - | - | - | - |
| | | | | | | | | | | |
| StandardStreamRead | .NET Core 2.1 | 60.618 us | 131.7038 us | 7.2191 us | 0.78 | 0.10 | - | - | - | - |
| BufferedReadStreamRead | .NET Core 2.1 | 30.006 us | 25.2499 us | 1.3840 us | 0.38 | 0.02 | - | - | - | - |
| BufferedReadStreamWrapRead | .NET Core 2.1 | 29.241 us | 6.5020 us | 0.3564 us | 0.37 | 0.01 | - | - | - | - |
| StandardStreamReadByte | .NET Core 2.1 | 78.074 us | 15.8463 us | 0.8686 us | 1.00 | 0.00 | - | - | - | - |
| BufferedReadStreamReadByte | .NET Core 2.1 | 14.737 us | 20.1510 us | 1.1045 us | 0.19 | 0.01 | - | - | - | - |
| BufferedReadStreamWrapReadByte | .NET Core 2.1 | 13.234 us | 1.4711 us | 0.0806 us | 0.17 | 0.00 | - | - | - | - |
| ArrayReadByte | .NET Core 2.1 | 9.373 us | 0.6108 us | 0.0335 us | 0.12 | 0.00 | - | - | - | - |
| | | | | | | | | | | |
| StandardStreamRead | .NET Core 3.1 | 52.151 us | 19.9456 us | 1.0933 us | 0.65 | 0.03 | - | - | - | - |
| BufferedReadStreamRead | .NET Core 3.1 | 29.217 us | 0.2490 us | 0.0136 us | 0.36 | 0.01 | - | - | - | - |
| BufferedReadStreamWrapRead | .NET Core 3.1 | 32.962 us | 7.1382 us | 0.3913 us | 0.41 | 0.02 | - | - | - | - |
| StandardStreamReadByte | .NET Core 3.1 | 80.310 us | 45.0350 us | 2.4685 us | 1.00 | 0.00 | - | - | - | - |
| BufferedReadStreamReadByte | .NET Core 3.1 | 13.092 us | 0.6268 us | 0.0344 us | 0.16 | 0.00 | - | - | - | - |
| BufferedReadStreamWrapReadByte | .NET Core 3.1 | 13.282 us | 3.8689 us | 0.2121 us | 0.17 | 0.01 | - | - | - | - |
| ArrayReadByte | .NET Core 3.1 | 9.349 us | 2.9860 us | 0.1637 us | 0.12 | 0.00 | - | - | - | - |
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------------------- |----------- |-------------- |-----------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:|
| StandardStreamRead | Job-OKZLUV | .NET 4.7.2 | 66.785 us | 15.768 us | 0.8643 us | 0.83 | 0.01 | - | - | - | - |
| BufferedReadStreamRead | Job-OKZLUV | .NET 4.7.2 | 97.389 us | 17.658 us | 0.9679 us | 1.21 | 0.01 | - | - | - | - |
| BufferedReadStreamChunkedRead | Job-OKZLUV | .NET 4.7.2 | 96.006 us | 16.286 us | 0.8927 us | 1.20 | 0.02 | - | - | - | - |
| BufferedReadStreamWrapRead | Job-OKZLUV | .NET 4.7.2 | 37.064 us | 14.640 us | 0.8024 us | 0.46 | 0.02 | - | - | - | - |
| StandardStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 80.315 us | 26.676 us | 1.4622 us | 1.00 | 0.00 | - | - | - | - |
| BufferedReadStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 118.706 us | 38.013 us | 2.0836 us | 1.48 | 0.00 | - | - | - | - |
| BufferedReadStreamChunkedReadByte | Job-OKZLUV | .NET 4.7.2 | 115.437 us | 33.352 us | 1.8282 us | 1.44 | 0.01 | - | - | - | - |
| BufferedReadStreamWrapReadByte | Job-OKZLUV | .NET 4.7.2 | 16.449 us | 11.400 us | 0.6249 us | 0.20 | 0.00 | - | - | - | - |
| ArrayReadByte | Job-OKZLUV | .NET 4.7.2 | 10.416 us | 1.866 us | 0.1023 us | 0.13 | 0.00 | - | - | - | - |
| | | | | | | | | | | | |
| StandardStreamRead | Job-CPYMXV | .NET Core 2.1 | 71.425 us | 50.441 us | 2.7648 us | 0.82 | 0.03 | - | - | - | - |
| BufferedReadStreamRead | Job-CPYMXV | .NET Core 2.1 | 32.816 us | 6.655 us | 0.3648 us | 0.38 | 0.01 | - | - | - | - |
| BufferedReadStreamChunkedRead | Job-CPYMXV | .NET Core 2.1 | 31.995 us | 7.751 us | 0.4249 us | 0.37 | 0.01 | - | - | - | - |
| BufferedReadStreamWrapRead | Job-CPYMXV | .NET Core 2.1 | 31.970 us | 4.170 us | 0.2286 us | 0.37 | 0.01 | - | - | - | - |
| StandardStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 86.909 us | 18.565 us | 1.0176 us | 1.00 | 0.00 | - | - | - | - |
| BufferedReadStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 14.596 us | 10.889 us | 0.5969 us | 0.17 | 0.01 | - | - | - | - |
| BufferedReadStreamChunkedReadByte | Job-CPYMXV | .NET Core 2.1 | 13.629 us | 1.569 us | 0.0860 us | 0.16 | 0.00 | - | - | - | - |
| BufferedReadStreamWrapReadByte | Job-CPYMXV | .NET Core 2.1 | 13.566 us | 1.743 us | 0.0956 us | 0.16 | 0.00 | - | - | - | - |
| ArrayReadByte | Job-CPYMXV | .NET Core 2.1 | 9.771 us | 6.658 us | 0.3650 us | 0.11 | 0.00 | - | - | - | - |
| | | | | | | | | | | | |
| StandardStreamRead | Job-BSGVGU | .NET Core 3.1 | 53.265 us | 65.819 us | 3.6078 us | 0.81 | 0.05 | - | - | - | - |
| BufferedReadStreamRead | Job-BSGVGU | .NET Core 3.1 | 33.163 us | 9.569 us | 0.5245 us | 0.51 | 0.01 | - | - | - | - |
| BufferedReadStreamChunkedRead | Job-BSGVGU | .NET Core 3.1 | 33.001 us | 6.114 us | 0.3351 us | 0.50 | 0.01 | - | - | - | - |
| BufferedReadStreamWrapRead | Job-BSGVGU | .NET Core 3.1 | 29.448 us | 7.120 us | 0.3902 us | 0.45 | 0.01 | - | - | - | - |
| StandardStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 65.619 us | 6.732 us | 0.3690 us | 1.00 | 0.00 | - | - | - | - |
| BufferedReadStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 13.989 us | 3.464 us | 0.1899 us | 0.21 | 0.00 | - | - | - | - |
| BufferedReadStreamChunkedReadByte | Job-BSGVGU | .NET Core 3.1 | 13.806 us | 1.710 us | 0.0938 us | 0.21 | 0.00 | - | - | - | - |
| BufferedReadStreamWrapReadByte | Job-BSGVGU | .NET Core 3.1 | 13.690 us | 1.523 us | 0.0835 us | 0.21 | 0.00 | - | - | - | - |
| ArrayReadByte | Job-BSGVGU | .NET Core 3.1 | 10.792 us | 8.228 us | 0.4510 us | 0.16 | 0.01 | - | - | - | - |
*/
}

379
tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

@ -0,0 +1,379 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests.IO
{
/// <summary>
/// Tests for the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
public class ChunkedMemoryStreamTests
{
private readonly MemoryAllocator allocator;
public ChunkedMemoryStreamTests()
{
this.allocator = Configuration.Default.MemoryAllocator;
}
[Fact]
public void MemoryStream_Ctor_InvalidCapacities()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new ChunkedMemoryStream(int.MinValue, this.allocator));
Assert.Throws<ArgumentOutOfRangeException>(() => new ChunkedMemoryStream(0, this.allocator));
}
[Fact]
public void MemoryStream_GetPositionTest_Negative()
{
using var ms = new ChunkedMemoryStream(this.allocator);
long iCurrentPos = ms.Position;
for (int i = -1; i > -6; i--)
{
Assert.Throws<ArgumentOutOfRangeException>(() => ms.Position = i);
Assert.Equal(ms.Position, iCurrentPos);
}
}
[Fact]
public void MemoryStream_ReadTest_Negative()
{
var ms2 = new ChunkedMemoryStream(this.allocator);
Assert.Throws<ArgumentNullException>(() => ms2.Read(null, 0, 0));
Assert.Throws<ArgumentOutOfRangeException>(() => ms2.Read(new byte[] { 1 }, -1, 0));
Assert.Throws<ArgumentOutOfRangeException>(() => ms2.Read(new byte[] { 1 }, 0, -1));
Assert.Throws<ArgumentException>(() => ms2.Read(new byte[] { 1 }, 2, 0));
Assert.Throws<ArgumentException>(() => ms2.Read(new byte[] { 1 }, 0, 2));
ms2.Dispose();
Assert.Throws<ObjectDisposedException>(() => ms2.Read(new byte[] { 1 }, 0, 1));
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
public void MemoryStream_ReadByteTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
using var cms = new ChunkedMemoryStream(this.allocator);
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], cms.ReadByte());
}
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
public void MemoryStream_ReadByteBufferTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
using var cms = new ChunkedMemoryStream(this.allocator);
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
var buffer = new byte[2];
for (int i = 0; i < expected.Length; i += 2)
{
cms.Read(buffer);
Assert.Equal(expected[i], buffer[0]);
Assert.Equal(expected[i + 1], buffer[1]);
}
}
[Theory]
[InlineData(ChunkedMemoryStream.DefaultBufferLength)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)]
public void MemoryStream_ReadByteBufferSpanTest(int length)
{
using MemoryStream ms = this.CreateTestStream(length);
using var cms = new ChunkedMemoryStream(this.allocator);
ms.CopyTo(cms);
cms.Position = 0;
var expected = ms.ToArray();
Span<byte> buffer = new byte[2];
for (int i = 0; i < expected.Length; i += 2)
{
cms.Read(buffer);
Assert.Equal(expected[i], buffer[0]);
Assert.Equal(expected[i + 1], buffer[1]);
}
}
[Fact]
public void MemoryStream_WriteToTests()
{
using (var ms2 = new ChunkedMemoryStream(this.allocator))
{
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
// [] Write to memoryStream, check the memoryStream
ms2.Write(bytArr, 0, bytArr.Length);
using var readonlyStream = new ChunkedMemoryStream(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
bytArrRet = new byte[(int)readonlyStream.Length];
readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
}
// [] Write to memoryStream, check the memoryStream
using (var ms2 = new ChunkedMemoryStream(this.allocator))
using (var ms3 = new ChunkedMemoryStream(this.allocator))
{
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
ms2.Write(bytArr, 0, bytArr.Length);
ms2.WriteTo(ms3);
ms3.Position = 0;
bytArrRet = new byte[(int)ms3.Length];
ms3.Read(bytArrRet, 0, (int)ms3.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
}
}
[Fact]
public void MemoryStream_WriteToSpanTests()
{
using (var ms2 = new ChunkedMemoryStream(this.allocator))
{
Span<byte> bytArrRet;
Span<byte> bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
// [] Write to memoryStream, check the memoryStream
ms2.Write(bytArr, 0, bytArr.Length);
using var readonlyStream = new ChunkedMemoryStream(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
bytArrRet = new byte[(int)readonlyStream.Length];
readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
}
// [] Write to memoryStream, check the memoryStream
using (var ms2 = new ChunkedMemoryStream(this.allocator))
using (var ms3 = new ChunkedMemoryStream(this.allocator))
{
Span<byte> bytArrRet;
Span<byte> bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
ms2.Write(bytArr, 0, bytArr.Length);
ms2.WriteTo(ms3);
ms3.Position = 0;
bytArrRet = new byte[(int)ms3.Length];
ms3.Read(bytArrRet, 0, (int)ms3.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
}
}
[Fact]
public void MemoryStream_WriteByteTests()
{
using (var ms2 = new ChunkedMemoryStream(this.allocator))
{
byte[] bytArrRet;
byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
for (int i = 0; i < bytArr.Length; i++)
{
ms2.WriteByte(bytArr[i]);
}
using var readonlyStream = new ChunkedMemoryStream(this.allocator);
ms2.WriteTo(readonlyStream);
readonlyStream.Flush();
readonlyStream.Position = 0;
bytArrRet = new byte[(int)readonlyStream.Length];
readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
for (int i = 0; i < bytArr.Length; i++)
{
Assert.Equal(bytArr[i], bytArrRet[i]);
}
}
}
[Fact]
public void MemoryStream_WriteToTests_Negative()
{
using var ms2 = new ChunkedMemoryStream(this.allocator);
Assert.Throws<ArgumentNullException>(() => ms2.WriteTo(null));
ms2.Write(new byte[] { 1 }, 0, 1);
var readonlyStream = new MemoryStream(new byte[1028], false);
Assert.Throws<NotSupportedException>(() => ms2.WriteTo(readonlyStream));
readonlyStream.Dispose();
// [] Pass in a closed stream
Assert.Throws<ObjectDisposedException>(() => ms2.WriteTo(readonlyStream));
}
[Fact]
public void MemoryStream_CopyTo_Invalid()
{
ChunkedMemoryStream memoryStream;
const string BufferSize = "bufferSize";
using (memoryStream = new ChunkedMemoryStream(this.allocator))
{
const string Destination = "destination";
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null));
// Validate the destination parameter first.
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0));
Assert.Throws<ArgumentNullException>(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1));
// Then bufferSize.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
}
// After the Stream is disposed, we should fail on all CopyTos.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated.
Assert.Throws<ArgumentOutOfRangeException>(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
ChunkedMemoryStream disposedStream = memoryStream;
// We should throw first for the source being disposed...
Assert.Throws<ObjectDisposedException>(() => memoryStream.CopyTo(disposedStream, 1));
// Then for the destination being disposed.
memoryStream = new ChunkedMemoryStream(this.allocator);
Assert.Throws<ObjectDisposedException>(() => memoryStream.CopyTo(disposedStream, 1));
memoryStream.Dispose();
}
[Theory]
[MemberData(nameof(CopyToData))]
public void CopyTo(Stream source, byte[] expected)
{
using var destination = new ChunkedMemoryStream(this.allocator);
source.CopyTo(destination);
Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end.
Assert.Equal(expected, destination.ToArray());
}
public static IEnumerable<string> GetAllTestImages()
{
IEnumerable<string> allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories)
.Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase));
var result = new List<string>();
foreach (string path in allImageFiles)
{
result.Add(path.Substring(TestEnvironment.InputImagesDirectoryFullPath.Length));
}
return result;
}
public static IEnumerable<string> AllTestImages = GetAllTestImages();
[Theory]
[WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)]
public void DecoderIntegrationTest<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (!TestEnvironment.Is64BitProcess)
{
return;
}
Image<TPixel> expected;
try
{
expected = provider.GetImage();
}
catch
{
// The image is invalid
return;
}
string fullPath = Path.Combine(
TestEnvironment.InputImagesDirectoryFullPath,
((TestImageProvider<TPixel>.FileProvider)provider).FilePath);
using FileStream fs = File.OpenRead(fullPath);
using var nonSeekableStream = new NonSeekableStream(fs);
var actual = Image.Load<TPixel>(nonSeekableStream);
ImageComparer.Exact.VerifySimilarity(expected, actual);
}
public static IEnumerable<object[]> CopyToData()
{
// Stream is positioned @ beginning of data
byte[] data1 = new byte[] { 1, 2, 3 };
var stream1 = new MemoryStream(data1);
yield return new object[] { stream1, data1 };
// Stream is positioned in the middle of data
byte[] data2 = new byte[] { 0xff, 0xf3, 0xf0 };
var stream2 = new MemoryStream(data2) { Position = 1 };
yield return new object[] { stream2, new byte[] { 0xf3, 0xf0 } };
// Stream is positioned after end of data
byte[] data3 = data2;
var stream3 = new MemoryStream(data3) { Position = data3.Length + 1 };
yield return new object[] { stream3, Array.Empty<byte>() };
}
private MemoryStream CreateTestStream(int length)
{
var buffer = new byte[length];
var random = new Random();
random.NextBytes(buffer);
return new MemoryStream(buffer);
}
}
}

42
tests/ImageSharp.Tests/IO/FixedCapacityPooledMemoryStreamTests.cs

@ -1,42 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tests.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.IO
{
public class FixedCapacityPooledMemoryStreamTests
{
private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator();
[Theory]
[InlineData(1)]
[InlineData(512)]
public void RentsManagedBuffer(int length)
{
MemoryStream ms = this.memoryAllocator.AllocateFixedCapacityMemoryStream(length);
Assert.Equal(length, this.memoryAllocator.AllocationLog.Single().Length);
ms.Dispose();
Assert.Equal(1, this.memoryAllocator.ReturnLog.Count);
}
[Theory]
[InlineData(42)]
[InlineData(2999)]
public void UsesRentedBuffer(int length)
{
using MemoryStream ms = this.memoryAllocator.AllocateFixedCapacityMemoryStream(length);
ms.TryGetBuffer(out ArraySegment<byte> buffer);
byte[] array = buffer.Array;
Assert.Equal(array.GetHashCode(), this.memoryAllocator.AllocationLog.Single().HashCodeOfBuffer);
ms.Write(new byte[] { 123 });
Assert.Equal(123, array[0]);
}
}
}

48
tests/ImageSharp.Tests/Image/ImageTests.Identify.cs

@ -89,6 +89,29 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Fact]
public void FromNonSeekableStream_GlobalConfiguration()
{
using var stream = new MemoryStream(this.ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream);
IImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type);
Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type);
}
[Fact]
public void FromNonSeekableStream_GlobalConfiguration_NoFormat()
{
using var stream = new MemoryStream(this.ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream);
IImageInfo info = Image.Identify(nonSeekableStream);
Assert.NotNull(info);
}
[Fact]
public void FromStream_CustomConfiguration()
{
@ -140,6 +163,31 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Fact]
public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat()
{
using var stream = new MemoryStream(this.ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream);
var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info);
}
[Fact]
public async Task FromNonSeekableStreamAsync_GlobalConfiguration()
{
using var stream = new MemoryStream(this.ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream);
var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
}
[Fact]
public async Task FromPathAsync_CustomConfiguration()
{

15
tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void NonSeekableStream()
{
var stream = new NoneSeekableStream(this.DataStream);
var stream = new NonSeekableStream(this.DataStream);
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, stream);
Assert.NotNull(img);
@ -47,6 +47,17 @@ namespace SixLabors.ImageSharp.Tests
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
}
[Fact]
public async Task NonSeekableStreamAsync()
{
var stream = new NonSeekableStream(this.DataStream);
Image<Rgba32> img = await Image.LoadAsync<Rgba32>(this.TopLevelConfiguration, stream);
Assert.NotNull(img);
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
}
[Fact]
public void Configuration_Stream_Decoder_Specific()
{

31
tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

@ -72,6 +72,37 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Theory]
[InlineData("test.png", "image/png")]
[InlineData("test.tga", "image/tga")]
[InlineData("test.bmp", "image/bmp")]
[InlineData("test.jpg", "image/jpeg")]
[InlineData("test.gif", "image/gif")]
public async Task SaveStreamWithMime(string filename, string mimeType)
{
using (var image = new Image<Rgba32>(5, 5))
{
string ext = Path.GetExtension(filename);
IImageFormat format = image.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
Assert.Equal(mimeType, format.DefaultMimeType);
using (var stream = new MemoryStream())
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);
await image.SaveAsync(asyncStream, format);
stream.Position = 0;
(Image Image, IImageFormat Format) imf = await Image.LoadWithFormatAsync(stream);
Assert.Equal(format, imf.Format);
Assert.Equal(mimeType, imf.Format.DefaultMimeType);
imf.Image.Dispose();
}
}
}
[Fact]
public async Task ThrowsWhenDisposed()
{

164
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -6,10 +6,10 @@ using System.Buffers;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -80,10 +80,52 @@ namespace SixLabors.ImageSharp.Tests
}
}
public sealed class CastMemoryManager<TFrom, TTo> : MemoryManager<TTo>
where TFrom : unmanaged
where TTo : unmanaged
{
private readonly Memory<TFrom> memory;
public CastMemoryManager(Memory<TFrom> memory)
{
this.memory = memory;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
return MemoryMarshal.Cast<TFrom, TTo>(this.memory.Span);
}
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
int byteOffset = elementIndex * Unsafe.SizeOf<TTo>();
int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf<TFrom>(), out int remainder);
if (remainder != 0)
{
ThrowHelper.ThrowArgumentException("The input index doesn't result in an aligned item access", nameof(elementIndex));
}
return this.memory.Slice(shiftedOffset).Pin();
}
/// <inheritdoc/>
public override void Unpin()
{
}
}
[Fact]
public void WrapMemory_CreatedImageIsCorrect()
{
Configuration cfg = Configuration.Default.Clone();
var cfg = Configuration.CreateDefaultInstance();
var metaData = new ImageMetadata();
var array = new Rgba32[25];
@ -173,6 +215,124 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Fact]
public void WrapMemory_FromBytes_CreatedImageIsCorrect()
{
var cfg = Configuration.CreateDefaultInstance();
var metaData = new ImageMetadata();
var array = new byte[25 * Unsafe.SizeOf<Rgba32>()];
var memory = new Memory<byte>(array);
using (var image = Image.WrapMemory<Rgba32>(cfg, memory, 5, 5, metaData))
{
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
ref Rgba32 pixel0 = ref imageSpan[0];
Assert.True(Unsafe.AreSame(ref Unsafe.As<byte, Rgba32>(ref array[0]), ref pixel0));
Assert.Equal(cfg, image.GetConfiguration());
Assert.Equal(metaData, image.Metadata);
}
}
[Fact]
public void WrapSystemDrawingBitmap_FromBytes_WhenObserved()
{
if (ShouldSkipBitmapTest)
{
return;
}
using (var bmp = new Bitmap(51, 23))
{
using (var memoryManager = new BitmapMemoryManager(bmp))
{
Memory<Bgra32> pixelMemory = memoryManager.Memory;
Memory<byte> byteMemory = new CastMemoryManager<Bgra32, byte>(pixelMemory).Memory;
Bgra32 bg = Color.Red;
Bgra32 fg = Color.Green;
using (var image = Image.WrapMemory<Bgra32>(byteMemory, bmp.Width, bmp.Height))
{
Span<Bgra32> pixelSpan = pixelMemory.Span;
Span<Bgra32> imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span;
// We can't compare the two Memory<T> instances directly as they wrap different memory managers.
// To check that the underlying data matches, we can just manually check their lenth, and the
// fact that a reference to the first pixel in both spans is actually the same memory location.
Assert.Equal(pixelSpan.Length, imageSpan.Length);
Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference()));
Assert.True(image.TryGetSinglePixelSpan(out imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
}
}
Assert.False(memoryManager.IsDisposed);
}
string fn = System.IO.Path.Combine(
TestEnvironment.ActualOutputDirectoryFullPath,
$"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp");
bmp.Save(fn, ImageFormat.Bmp);
}
}
[Theory]
[InlineData(0, 5, 5)]
[InlineData(20, 5, 5)]
[InlineData(26, 5, 5)]
[InlineData(2, 1, 1)]
[InlineData(1023, 32, 32)]
public void WrapMemory_MemoryOfT_InvalidSize(int size, int height, int width)
{
var array = new Rgba32[size];
var memory = new Memory<Rgba32>(array);
Assert.Throws<ArgumentException>(() => Image.WrapMemory(memory, height, width));
}
private class TestMemoryOwner<T> : IMemoryOwner<T>
{
public Memory<T> Memory { get; set; }
public void Dispose()
{
}
}
[Theory]
[InlineData(0, 5, 5)]
[InlineData(20, 5, 5)]
[InlineData(26, 5, 5)]
[InlineData(2, 1, 1)]
[InlineData(1023, 32, 32)]
public void WrapMemory_IMemoryOwnerOfT_InvalidSize(int size, int height, int width)
{
var array = new Rgba32[size];
var memory = new TestMemoryOwner<Rgba32> { Memory = array };
Assert.Throws<ArgumentException>(() => Image.WrapMemory(memory, height, width));
}
[Theory]
[InlineData(0, 5, 5)]
[InlineData(20, 5, 5)]
[InlineData(26, 5, 5)]
[InlineData(2, 1, 1)]
[InlineData(1023, 32, 32)]
public void WrapMemory_MemoryOfByte_InvalidSize(int size, int height, int width)
{
var array = new byte[size * Unsafe.SizeOf<Rgba32>()];
var memory = new Memory<byte>(array);
Assert.Throws<ArgumentException>(() => Image.WrapMemory<Rgba32>(memory, height, width));
}
private static bool ShouldSkipBitmapTest =>
!TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1");
}

43
tests/ImageSharp.Tests/Image/NonSeekableStream.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Tests
{
internal class NonSeekableStream : Stream
{
private readonly Stream dataStream;
public NonSeekableStream(Stream dataStream)
=> this.dataStream = dataStream;
public override bool CanRead => this.dataStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override void Flush() => this.dataStream.Flush();
public override int Read(byte[] buffer, int offset, int count) => this.dataStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotImplementedException();
}
}

53
tests/ImageSharp.Tests/Image/NoneSeekableStream.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Tests
{
internal class NoneSeekableStream : Stream
{
private Stream dataStream;
public NoneSeekableStream(Stream dataStream)
{
this.dataStream = dataStream;
}
public override bool CanRead => this.dataStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => this.dataStream.Length;
public override long Position { get => this.dataStream.Position; set => throw new NotImplementedException(); }
public override void Flush()
{
this.dataStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return this.dataStream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
}

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -14,7 +14,7 @@
<ItemGroup>
<DotNetCliToolReference Include="dotnet-xunit" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" PublicKey="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
</ItemGroup>
<ItemGroup>

13
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -35,6 +35,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j
0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]";
[Theory]
[InlineData(-10, 2, 3f)]
[InlineData(-1, 2, 3f)]
[InlineData(0, 2, 3f)]
[InlineData(20, -1, 3f)]
[InlineData(20, -0, 3f)]
[InlineData(20, 4, -10f)]
[InlineData(20, 4, 0f)]
public void VerifyBokehBlurProcessorArguments_Fail(int radius, int components, float gamma)
{
Assert.Throws<ArgumentOutOfRangeException>(() => new BokehBlurProcessor(radius, components, gamma));
}
[Fact]
public void VerifyComplexComponents()
{

24
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -621,5 +621,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}));
}
}
[Theory]
[InlineData(1, 1)]
[InlineData(4, 6)]
[InlineData(2, 10)]
[InlineData(8, 1)]
[InlineData(3, 7)]
public void Issue1342(int width, int height)
{
using (var image = new Image<Rgba32>(1, 1))
{
var size = new Size(width, height);
image.Mutate(x => x
.Resize(
new ResizeOptions
{
Size = size,
Sampler = KnownResamplers.NearestNeighbor
}));
Assert.Equal(width, image.Width);
Assert.Equal(height, image.Height);
}
}
}
}

2
tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests
public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
where TPixel : unmanaged, IPixel<TPixel>
{
private class FileProvider : TestImageProvider<TPixel>, IXunitSerializable
internal class FileProvider : TestImageProvider<TPixel>, IXunitSerializable
{
// Need PixelTypes in the dictionary key, because result images of TestImageProvider<TPixel>.FileProvider
// are shared between PixelTypes.Color & PixelTypes.Rgba32

Loading…
Cancel
Save