mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
47 changed files with 1443 additions and 484 deletions
@ -1,202 +1,196 @@ |
|||||
name: Build |
name: Build |
||||
|
|
||||
on: |
on: |
||||
push: |
push: |
||||
branches: |
branches: |
||||
- master |
- master |
||||
tags: |
tags: |
||||
- "v*" |
- "v*" |
||||
pull_request: |
pull_request: |
||||
branches: |
branches: |
||||
- master |
- master |
||||
jobs: |
jobs: |
||||
Build: |
Build: |
||||
strategy: |
strategy: |
||||
matrix: |
matrix: |
||||
options: |
options: |
||||
- os: ubuntu-latest |
- os: ubuntu-latest |
||||
framework: net6.0 |
framework: net6.0 |
||||
sdk: 6.0.x |
sdk: 6.0.x |
||||
sdk-preview: true |
sdk-preview: true |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: macos-latest |
- os: macos-latest |
||||
framework: net6.0 |
framework: net6.0 |
||||
sdk: 6.0.x |
sdk: 6.0.x |
||||
sdk-preview: true |
sdk-preview: true |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net6.0 |
framework: net6.0 |
||||
sdk: 6.0.x |
sdk: 6.0.x |
||||
sdk-preview: true |
sdk-preview: true |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: ubuntu-latest |
- os: ubuntu-latest |
||||
framework: net5.0 |
framework: net5.0 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: macos-latest |
- os: macos-latest |
||||
framework: net5.0 |
framework: net5.0 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net5.0 |
framework: net5.0 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: ubuntu-latest |
- os: ubuntu-latest |
||||
framework: netcoreapp3.1 |
framework: netcoreapp3.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: true |
codecov: false |
||||
- os: macos-latest |
- os: macos-latest |
||||
framework: netcoreapp3.1 |
framework: netcoreapp3.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: netcoreapp3.1 |
framework: netcoreapp3.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: netcoreapp2.1 |
framework: netcoreapp2.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net472 |
framework: net472 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net472 |
framework: net472 |
||||
runtime: -x86 |
runtime: -x86 |
||||
codecov: false |
codecov: false |
||||
|
|
||||
runs-on: ${{matrix.options.os}} |
runs-on: ${{matrix.options.os}} |
||||
|
|
||||
steps: |
steps: |
||||
- name: Git Config |
- name: Git Config |
||||
shell: bash |
shell: bash |
||||
run: | |
run: | |
||||
git config --global core.autocrlf false |
git config --global core.autocrlf false |
||||
git config --global core.longpaths true |
git config --global core.longpaths true |
||||
|
|
||||
- name: Git Checkout |
- name: Git Checkout |
||||
uses: actions/checkout@v2 |
uses: actions/checkout@v2 |
||||
with: |
with: |
||||
fetch-depth: 0 |
fetch-depth: 0 |
||||
submodules: recursive |
submodules: recursive |
||||
|
|
||||
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 |
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 |
||||
- name: Git Create LFS FileList |
- name: Git Create LFS FileList |
||||
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id |
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id |
||||
|
|
||||
- name: Git Setup LFS Cache |
- name: Git Setup LFS Cache |
||||
uses: actions/cache@v2 |
uses: actions/cache@v2 |
||||
id: lfs-cache |
id: lfs-cache |
||||
with: |
with: |
||||
path: .git/lfs |
path: .git/lfs |
||||
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 |
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 |
||||
|
|
||||
- name: Git Pull LFS |
- name: Git Pull LFS |
||||
run: git lfs pull |
run: git lfs pull |
||||
|
|
||||
- name: NuGet Install |
- name: NuGet Install |
||||
uses: NuGet/setup-nuget@v1 |
uses: NuGet/setup-nuget@v1 |
||||
|
|
||||
- name: NuGet Setup Cache |
- name: NuGet Setup Cache |
||||
uses: actions/cache@v2 |
uses: actions/cache@v2 |
||||
id: nuget-cache |
id: nuget-cache |
||||
with: |
with: |
||||
path: ~/.nuget |
path: ~/.nuget |
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
||||
restore-keys: ${{ runner.os }}-nuget- |
restore-keys: ${{ runner.os }}-nuget- |
||||
|
|
||||
- name: DotNet Setup Preview |
- name: DotNet Setup Preview |
||||
if: ${{ matrix.options.sdk-preview == true }} |
if: ${{ matrix.options.sdk-preview == true }} |
||||
uses: actions/setup-dotnet@v1 |
uses: actions/setup-dotnet@v1 |
||||
with: |
with: |
||||
dotnet-version: ${{ matrix.options.sdk }} |
dotnet-version: ${{ matrix.options.sdk }} |
||||
include-prerelease: true |
include-prerelease: true |
||||
|
|
||||
- name: DotNet Build |
- name: DotNet Build |
||||
if: ${{ matrix.options.sdk-preview != true }} |
if: ${{ matrix.options.sdk-preview != true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING: True |
SIXLABORS_TESTING: True |
||||
|
|
||||
- name: DotNet Build Preview |
- name: DotNet Build Preview |
||||
if: ${{ matrix.options.sdk-preview == true }} |
if: ${{ matrix.options.sdk-preview == true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING_PREVIEW: True |
SIXLABORS_TESTING_PREVIEW: True |
||||
|
|
||||
- name: DotNet Test |
- name: DotNet Test |
||||
if: ${{ matrix.options.sdk-preview != true }} |
if: ${{ matrix.options.sdk-preview != true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING: True |
SIXLABORS_TESTING: True |
||||
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
||||
|
|
||||
- name: DotNet Test Preview |
- name: DotNet Test Preview |
||||
if: ${{ matrix.options.sdk-preview == true }} |
if: ${{ matrix.options.sdk-preview == true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING_PREVIEW: True |
SIXLABORS_TESTING_PREVIEW: True |
||||
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
||||
|
|
||||
- name: Export Failed Output |
- name: Export Failed Output |
||||
uses: actions/upload-artifact@v2 |
uses: actions/upload-artifact@v2 |
||||
if: failure() |
if: failure() |
||||
with: |
with: |
||||
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip |
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip |
||||
path: tests/Images/ActualOutput/ |
path: tests/Images/ActualOutput/ |
||||
|
|
||||
- name: Codecov Update |
Publish: |
||||
uses: codecov/codecov-action@v1 |
needs: [Build] |
||||
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') |
|
||||
with: |
runs-on: ubuntu-latest |
||||
flags: unittests |
|
||||
|
if: (github.event_name == 'push') |
||||
Publish: |
|
||||
needs: [Build] |
steps: |
||||
|
- name: Git Config |
||||
runs-on: ubuntu-latest |
shell: bash |
||||
|
run: | |
||||
if: (github.event_name == 'push') |
git config --global core.autocrlf false |
||||
|
git config --global core.longpaths true |
||||
steps: |
|
||||
- name: Git Config |
- name: Git Checkout |
||||
shell: bash |
uses: actions/checkout@v2 |
||||
run: | |
with: |
||||
git config --global core.autocrlf false |
fetch-depth: 0 |
||||
git config --global core.longpaths true |
submodules: recursive |
||||
|
|
||||
- name: Git Checkout |
- name: NuGet Install |
||||
uses: actions/checkout@v2 |
uses: NuGet/setup-nuget@v1 |
||||
with: |
|
||||
fetch-depth: 0 |
- name: NuGet Setup Cache |
||||
submodules: recursive |
uses: actions/cache@v2 |
||||
|
id: nuget-cache |
||||
- name: NuGet Install |
with: |
||||
uses: NuGet/setup-nuget@v1 |
path: ~/.nuget |
||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
||||
- name: NuGet Setup Cache |
restore-keys: ${{ runner.os }}-nuget- |
||||
uses: actions/cache@v2 |
|
||||
id: nuget-cache |
- name: DotNet Pack |
||||
with: |
shell: pwsh |
||||
path: ~/.nuget |
run: ./ci-pack.ps1 |
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
|
||||
restore-keys: ${{ runner.os }}-nuget- |
- name: MyGet Publish |
||||
|
shell: pwsh |
||||
- name: DotNet Pack |
run: | |
||||
shell: pwsh |
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package |
||||
run: ./ci-pack.ps1 |
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json |
||||
|
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org |
||||
- name: MyGet Publish |
|
||||
shell: pwsh |
|
||||
run: | |
|
||||
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package |
|
||||
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json |
|
||||
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org |
|
||||
|
|||||
@ -0,0 +1,81 @@ |
|||||
|
name: CodeCoverage |
||||
|
|
||||
|
on: |
||||
|
schedule: |
||||
|
# 2AM every Tuesday/Thursday |
||||
|
- cron: "0 2 * * 2,4" |
||||
|
jobs: |
||||
|
Build: |
||||
|
strategy: |
||||
|
matrix: |
||||
|
options: |
||||
|
- os: ubuntu-latest |
||||
|
framework: netcoreapp3.1 |
||||
|
runtime: -x64 |
||||
|
codecov: true |
||||
|
|
||||
|
runs-on: ${{matrix.options.os}} |
||||
|
|
||||
|
steps: |
||||
|
- name: Git Config |
||||
|
shell: bash |
||||
|
run: | |
||||
|
git config --global core.autocrlf false |
||||
|
git config --global core.longpaths true |
||||
|
|
||||
|
- name: Git Checkout |
||||
|
uses: actions/checkout@v2 |
||||
|
with: |
||||
|
fetch-depth: 0 |
||||
|
submodules: recursive |
||||
|
|
||||
|
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 |
||||
|
- name: Git Create LFS FileList |
||||
|
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id |
||||
|
|
||||
|
- name: Git Setup LFS Cache |
||||
|
uses: actions/cache@v2 |
||||
|
id: lfs-cache |
||||
|
with: |
||||
|
path: .git/lfs |
||||
|
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 |
||||
|
|
||||
|
- name: Git Pull LFS |
||||
|
run: git lfs pull |
||||
|
|
||||
|
- name: NuGet Install |
||||
|
uses: NuGet/setup-nuget@v1 |
||||
|
|
||||
|
- name: NuGet Setup Cache |
||||
|
uses: actions/cache@v2 |
||||
|
id: nuget-cache |
||||
|
with: |
||||
|
path: ~/.nuget |
||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
||||
|
restore-keys: ${{ runner.os }}-nuget- |
||||
|
|
||||
|
- name: DotNet Build |
||||
|
shell: pwsh |
||||
|
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
||||
|
env: |
||||
|
SIXLABORS_TESTING: True |
||||
|
|
||||
|
- name: DotNet Test |
||||
|
shell: pwsh |
||||
|
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
||||
|
env: |
||||
|
SIXLABORS_TESTING: True |
||||
|
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
||||
|
|
||||
|
- name: Export Failed Output |
||||
|
uses: actions/upload-artifact@v2 |
||||
|
if: failure() |
||||
|
with: |
||||
|
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip |
||||
|
path: tests/Images/ActualOutput/ |
||||
|
|
||||
|
- name: Codecov Update |
||||
|
uses: codecov/codecov-action@v1 |
||||
|
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') |
||||
|
with: |
||||
|
flags: unittests |
||||
@ -0,0 +1,97 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Xmp; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Gif |
||||
|
{ |
||||
|
internal readonly struct GifXmpApplicationExtension : IGifExtension |
||||
|
{ |
||||
|
public GifXmpApplicationExtension(byte[] data) => this.Data = data; |
||||
|
|
||||
|
public byte Label => GifConstants.ApplicationExtensionLabel; |
||||
|
|
||||
|
public int ContentLength => this.Data.Length + 269; // 12 + Data Length + 1 + 256
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the raw Data.
|
||||
|
/// </summary>
|
||||
|
public byte[] Data { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads the XMP metadata from the specified stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream to read from.</param>
|
||||
|
/// <returns>The XMP metadata</returns>
|
||||
|
/// <exception cref="ImageFormatException">Thrown if the XMP block is not properly terminated.</exception>
|
||||
|
public static GifXmpApplicationExtension Read(Stream stream) |
||||
|
{ |
||||
|
// Read data in blocks, until an \0 character is encountered.
|
||||
|
// We overshoot, indicated by the terminatorIndex variable.
|
||||
|
const int bufferSize = 256; |
||||
|
var list = new List<byte[]>(); |
||||
|
int terminationIndex = -1; |
||||
|
while (terminationIndex < 0) |
||||
|
{ |
||||
|
byte[] temp = new byte[bufferSize]; |
||||
|
int bytesRead = stream.Read(temp); |
||||
|
list.Add(temp); |
||||
|
terminationIndex = Array.IndexOf(temp, (byte)1); |
||||
|
} |
||||
|
|
||||
|
// Pack all the blocks (except magic trailer) into one single array again.
|
||||
|
int dataSize = ((list.Count - 1) * bufferSize) + terminationIndex; |
||||
|
byte[] buffer = new byte[dataSize]; |
||||
|
Span<byte> bufferSpan = buffer; |
||||
|
int pos = 0; |
||||
|
for (int j = 0; j < list.Count - 1; j++) |
||||
|
{ |
||||
|
list[j].CopyTo(bufferSpan.Slice(pos)); |
||||
|
pos += bufferSize; |
||||
|
} |
||||
|
|
||||
|
// Last one only needs the portion until terminationIndex copied over.
|
||||
|
Span<byte> lastBytes = list[list.Count - 1]; |
||||
|
lastBytes.Slice(0, terminationIndex).CopyTo(bufferSpan.Slice(pos)); |
||||
|
|
||||
|
// Skip the remainder of the magic trailer.
|
||||
|
stream.Skip(258 - (bufferSize - terminationIndex)); |
||||
|
return new GifXmpApplicationExtension(buffer); |
||||
|
} |
||||
|
|
||||
|
public int WriteTo(Span<byte> buffer) |
||||
|
{ |
||||
|
int totalSize = this.ContentLength; |
||||
|
if (buffer.Length < totalSize) |
||||
|
{ |
||||
|
throw new InsufficientMemoryException("Unable to write XMP metadata to GIF image"); |
||||
|
} |
||||
|
|
||||
|
int bytesWritten = 0; |
||||
|
buffer[bytesWritten++] = GifConstants.ApplicationBlockSize; |
||||
|
|
||||
|
// Write "XMP DataXMP"
|
||||
|
ReadOnlySpan<byte> idBytes = GifConstants.XmpApplicationIdentificationBytes; |
||||
|
idBytes.CopyTo(buffer.Slice(bytesWritten)); |
||||
|
bytesWritten += idBytes.Length; |
||||
|
|
||||
|
// XMP Data itself
|
||||
|
this.Data.CopyTo(buffer.Slice(bytesWritten)); |
||||
|
bytesWritten += this.Data.Length; |
||||
|
|
||||
|
// Write the Magic Trailer
|
||||
|
buffer[bytesWritten++] = 0x01; |
||||
|
for (byte i = 255; i > 0; i--) |
||||
|
{ |
||||
|
buffer[bytesWritten++] = i; |
||||
|
} |
||||
|
|
||||
|
buffer[bytesWritten++] = 0x00; |
||||
|
|
||||
|
return totalSize; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Xml.Linq; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an XMP profile, providing access to the raw XML.
|
||||
|
/// See <seealso href="https://www.adobe.com/devnet/xmp.html"/> for the full specification.
|
||||
|
/// </summary>
|
||||
|
public sealed class XmpProfile : IDeepCloneable<XmpProfile> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="XmpProfile"/> class.
|
||||
|
/// </summary>
|
||||
|
public XmpProfile() |
||||
|
: this((byte[])null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="XmpProfile"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
|
||||
|
public XmpProfile(byte[] data) => this.Data = data; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="XmpProfile"/> class
|
||||
|
/// by making a copy from another XMP profile.
|
||||
|
/// </summary>
|
||||
|
/// <param name="other">The other XMP profile, from which the clone should be made from.</param>
|
||||
|
private XmpProfile(XmpProfile other) |
||||
|
{ |
||||
|
Guard.NotNull(other, nameof(other)); |
||||
|
|
||||
|
this.Data = other.Data; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the XMP raw data byte array.
|
||||
|
/// </summary>
|
||||
|
internal byte[] Data { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the raw XML document containing the XMP profile.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="XDocument"/></returns>
|
||||
|
public XDocument GetDocument() |
||||
|
{ |
||||
|
byte[] byteArray = this.Data; |
||||
|
if (byteArray is null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// Strip leading whitespace, as the XmlReader doesn't like them.
|
||||
|
int count = byteArray.Length; |
||||
|
for (int i = count - 1; i > 0; i--) |
||||
|
{ |
||||
|
if (byteArray[i] is 0 or 0x0f) |
||||
|
{ |
||||
|
count--; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
using var stream = new MemoryStream(byteArray, 0, count); |
||||
|
using var reader = new StreamReader(stream, Encoding.UTF8); |
||||
|
return XDocument.Load(reader); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Convert the content of this <see cref="XmpProfile"/> into a byte array.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="T:byte[]"/></returns>
|
||||
|
public byte[] ToByteArray() |
||||
|
{ |
||||
|
byte[] result = new byte[this.Data.Length]; |
||||
|
this.Data.AsSpan().CopyTo(result); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public XmpProfile DeepClone() => new(this); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,260 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Xml.Linq; |
||||
|
using SixLabors.ImageSharp.Formats; |
||||
|
using SixLabors.ImageSharp.Formats.Gif; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg; |
||||
|
using SixLabors.ImageSharp.Formats.Png; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff; |
||||
|
using SixLabors.ImageSharp.Formats.Webp; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Xmp; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp |
||||
|
{ |
||||
|
public class XmpProfileTests |
||||
|
{ |
||||
|
private static GifDecoder GifDecoder => new() { IgnoreMetadata = false }; |
||||
|
|
||||
|
private static JpegDecoder JpegDecoder => new() { IgnoreMetadata = false }; |
||||
|
|
||||
|
private static PngDecoder PngDecoder => new() { IgnoreMetadata = false }; |
||||
|
|
||||
|
private static TiffDecoder TiffDecoder => new() { IgnoreMetadata = false }; |
||||
|
|
||||
|
private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false }; |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] |
||||
|
public async void ReadXmpMetadata_FromGif_Works<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = await provider.GetImageAsync(GifDecoder)) |
||||
|
{ |
||||
|
XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32)] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Metadata, PixelTypes.Rgba32)] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.ExtendedXmp, PixelTypes.Rgba32)] |
||||
|
public async void ReadXmpMetadata_FromJpg_Works<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = await provider.GetImageAsync(JpegDecoder)) |
||||
|
{ |
||||
|
XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Png.XmpColorPalette, PixelTypes.Rgba32)] |
||||
|
public async void ReadXmpMetadata_FromPng_Works<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = await provider.GetImageAsync(PngDecoder)) |
||||
|
{ |
||||
|
XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)] |
||||
|
public async void ReadXmpMetadata_FromTiff_Works<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = await provider.GetImageAsync(TiffDecoder)) |
||||
|
{ |
||||
|
XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32)] |
||||
|
public async void ReadXmpMetadata_FromWebp_Works<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = await provider.GetImageAsync(WebpDecoder)) |
||||
|
{ |
||||
|
XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void XmpProfile_ToFromByteArray_ReturnsClone() |
||||
|
{ |
||||
|
// arrange
|
||||
|
XmpProfile profile = CreateMinimalXmlProfile(); |
||||
|
byte[] original = profile.ToByteArray(); |
||||
|
|
||||
|
// act
|
||||
|
byte[] actual = profile.ToByteArray(); |
||||
|
|
||||
|
// assert
|
||||
|
Assert.False(ReferenceEquals(original, actual)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void XmpProfile_CloneIsDeep() |
||||
|
{ |
||||
|
// arrange
|
||||
|
XmpProfile profile = CreateMinimalXmlProfile(); |
||||
|
byte[] original = profile.ToByteArray(); |
||||
|
|
||||
|
// act
|
||||
|
XmpProfile clone = profile.DeepClone(); |
||||
|
byte[] actual = clone.ToByteArray(); |
||||
|
|
||||
|
// assert
|
||||
|
Assert.False(ReferenceEquals(original, actual)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WritingGif_PreservesXmpProfile() |
||||
|
{ |
||||
|
// arrange
|
||||
|
var image = new Image<Rgba32>(1, 1); |
||||
|
XmpProfile original = CreateMinimalXmlProfile(); |
||||
|
image.Metadata.XmpProfile = original; |
||||
|
var encoder = new GifEncoder(); |
||||
|
|
||||
|
// act
|
||||
|
using Image<Rgba32> reloadedImage = WriteAndRead(image, encoder); |
||||
|
|
||||
|
// assert
|
||||
|
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
Assert.Equal(original.Data, actual.Data); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WritingJpeg_PreservesXmpProfile() |
||||
|
{ |
||||
|
// arrange
|
||||
|
var image = new Image<Rgba32>(1, 1); |
||||
|
XmpProfile original = CreateMinimalXmlProfile(); |
||||
|
image.Metadata.XmpProfile = original; |
||||
|
var encoder = new JpegEncoder(); |
||||
|
|
||||
|
// act
|
||||
|
using Image<Rgba32> reloadedImage = WriteAndRead(image, encoder); |
||||
|
|
||||
|
// assert
|
||||
|
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
Assert.Equal(original.Data, actual.Data); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async void WritingJpeg_PreservesExtendedXmpProfile() |
||||
|
{ |
||||
|
// arrange
|
||||
|
var provider = TestImageProvider<Rgba32>.File(TestImages.Jpeg.Baseline.ExtendedXmp); |
||||
|
using Image<Rgba32> image = await provider.GetImageAsync(JpegDecoder); |
||||
|
XmpProfile original = image.Metadata.XmpProfile; |
||||
|
var encoder = new JpegEncoder(); |
||||
|
|
||||
|
// act
|
||||
|
using Image<Rgba32> reloadedImage = WriteAndRead(image, encoder); |
||||
|
|
||||
|
// assert
|
||||
|
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
Assert.Equal(original.Data, actual.Data); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WritingPng_PreservesXmpProfile() |
||||
|
{ |
||||
|
// arrange
|
||||
|
var image = new Image<Rgba32>(1, 1); |
||||
|
XmpProfile original = CreateMinimalXmlProfile(); |
||||
|
image.Metadata.XmpProfile = original; |
||||
|
var encoder = new PngEncoder(); |
||||
|
|
||||
|
// act
|
||||
|
using Image<Rgba32> reloadedImage = WriteAndRead(image, encoder); |
||||
|
|
||||
|
// assert
|
||||
|
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
Assert.Equal(original.Data, actual.Data); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WritingTiff_PreservesXmpProfile() |
||||
|
{ |
||||
|
// arrange
|
||||
|
var image = new Image<Rgba32>(1, 1); |
||||
|
XmpProfile original = CreateMinimalXmlProfile(); |
||||
|
image.Frames.RootFrame.Metadata.XmpProfile = original; |
||||
|
var encoder = new TiffEncoder(); |
||||
|
|
||||
|
// act
|
||||
|
using Image<Rgba32> reloadedImage = WriteAndRead(image, encoder); |
||||
|
|
||||
|
// assert
|
||||
|
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
Assert.Equal(original.Data, actual.Data); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WritingWebp_PreservesXmpProfile() |
||||
|
{ |
||||
|
// arrange
|
||||
|
var image = new Image<Rgba32>(1, 1); |
||||
|
XmpProfile original = CreateMinimalXmlProfile(); |
||||
|
image.Metadata.XmpProfile = original; |
||||
|
var encoder = new WebpEncoder(); |
||||
|
|
||||
|
// act
|
||||
|
using Image<Rgba32> reloadedImage = WriteAndRead(image, encoder); |
||||
|
|
||||
|
// assert
|
||||
|
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; |
||||
|
XmpProfileContainsExpectedValues(actual); |
||||
|
Assert.Equal(original.Data, actual.Data); |
||||
|
} |
||||
|
|
||||
|
private static void XmpProfileContainsExpectedValues(XmpProfile xmp) |
||||
|
{ |
||||
|
Assert.NotNull(xmp); |
||||
|
XDocument document = xmp.GetDocument(); |
||||
|
Assert.NotNull(document); |
||||
|
Assert.Equal("xmpmeta", document.Root.Name.LocalName); |
||||
|
Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); |
||||
|
} |
||||
|
|
||||
|
private static XmpProfile CreateMinimalXmlProfile() |
||||
|
{ |
||||
|
string content = $"<?xpacket begin='' id='{Guid.NewGuid()}'?><x:xmpmeta xmlns:x='adobe:ns:meta/'></x:xmpmeta><?xpacket end='w'?> "; |
||||
|
byte[] data = Encoding.UTF8.GetBytes(content); |
||||
|
var profile = new XmpProfile(data); |
||||
|
return profile; |
||||
|
} |
||||
|
|
||||
|
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, IImageEncoder encoder) |
||||
|
{ |
||||
|
using (var memStream = new MemoryStream()) |
||||
|
{ |
||||
|
image.Save(memStream, encoder); |
||||
|
image.Dispose(); |
||||
|
|
||||
|
memStream.Position = 0; |
||||
|
return Image.Load<Rgba32>(memStream); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:800911efb6f0c796d61b5ea14fc67fe891aaae3c04a49cfd5b86e68958598436 |
||||
|
size 138810 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:000c67f210059b101570949e889846bb11d6bdc801bd641d7d26424ad9cd027f |
||||
|
size 623986 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:3654c48003b85c1110bad8c31d2f94eaf4dcfe488698246b3ead4b54715d8d18 |
||||
|
size 1325 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:fb55607fd7de6a47d8dd242c1a7be9627c564821554db896ed46603d15963c06 |
||||
|
size 1025 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:755a63652695d7e190f375c9c0697cd37c9b601cd54405c704ec8efc200e67fc |
||||
|
size 474772 |
||||
Loading…
Reference in new issue