Browse Source

Add YCbCrColor and intial encoder tests

Former-commit-id: 7240b4fba080dba719b8add3eebba027dd8c350d
Former-commit-id: 3129efd3d7f565fffd52e4ce970ad739e58cd636
Former-commit-id: dbaca5b79a4e4d023ee9d508a19a4e83f0f71aad
af/merge-core
James South 11 years ago
parent
commit
8e2df8cf22
  1. 6
      .nuget/NuGet.Config
  2. 1
      .nuget/NuGet.exe.REMOVED.git-id
  3. 144
      .nuget/NuGet.targets
  4. 3
      ImageProcessor.sln
  5. 34
      src/ImageProcessor/Colors/Color.cs
  6. 223
      src/ImageProcessor/Colors/YCbCrColor.cs
  7. 25
      src/ImageProcessor/Common/Extensions/ComparableExtensions.cs
  8. 42
      src/ImageProcessor/Common/Helpers/Guard.cs
  9. 13
      src/ImageProcessor/Formats/Gif/GifDecoder.cs
  10. 4
      src/ImageProcessor/Formats/Png/PngDecoderCore.cs
  11. 6
      src/ImageProcessor/Formats/Png/PngEncoder.cs
  12. 294
      src/ImageProcessor/Image.cs
  13. 18
      src/ImageProcessor/ImageBase.cs
  14. 11
      src/ImageProcessor/ImageProcessor.csproj
  15. 1
      src/ImageProcessor/ImageProcessor.csproj.DotSettings
  16. 2
      src/ImageProcessor/Settings.StyleCop
  17. 89
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
  18. 60
      tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
  19. 6
      tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj
  20. 1
      tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings
  21. 3
      tests/ImageProcessor.Tests/TestImages/Formats/Png/cmyk.png
  22. 1
      tests/ImageProcessor.Tests/packages.config

6
.nuget/NuGet.Config

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>

1
.nuget/NuGet.exe.REMOVED.git-id

@ -0,0 +1 @@
9ca66594f912a1fe7aec510819006fb1a80bc1a9

144
.nuget/NuGet.targets

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
<!-- Enable the restore command to run before builds -->
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
<!-- Property that enables building a package from a project -->
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
<!-- Determines if package restore consent is required to restore packages -->
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
<!-- Download NuGet.exe if it does not already exist -->
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
<!--
<PackageSource Include="https://www.nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
</ItemGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
<!-- Windows specific commands -->
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
</PropertyGroup>
<PropertyGroup>
<PackagesProjectConfig Condition=" '$(OS)' == 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config</PackagesProjectConfig>
<PackagesProjectConfig Condition=" '$(OS)' != 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config</PackagesProjectConfig>
</PropertyGroup>
<PropertyGroup>
<PackagesConfig Condition="Exists('$(MSBuildProjectDirectory)\packages.config')">$(MSBuildProjectDirectory)\packages.config</PackagesConfig>
<PackagesConfig Condition="Exists('$(PackagesProjectConfig)')">$(PackagesProjectConfig)</PackagesConfig>
</PropertyGroup>
<PropertyGroup>
<!-- NuGet command -->
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 "$(NuGetExePath)"</NuGetCommand>
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>
<!-- Commands -->
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
RestorePackages;
$(BuildDependsOn);
</BuildDependsOn>
<!-- Make the build depend on restore packages -->
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
$(BuildDependsOn);
BuildPackage;
</BuildDependsOn>
</PropertyGroup>
<Target Name="CheckPrerequisites">
<!-- Raise an error if we're unable to locate nuget.exe -->
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
<!--
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
This effectively acts as a lock that makes sure that the download operation will only happen once and all
parallel builds will have to wait for it to complete.
-->
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
</Target>
<Target Name="_DownloadNuGet">
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
</Target>
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(RestoreCommand)"
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
<Exec Command="$(RestoreCommand)"
LogStandardErrorAsError="true"
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
</Target>
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(BuildCommand)"
Condition=" '$(OS)' != 'Windows_NT' " />
<Exec Command="$(BuildCommand)"
LogStandardErrorAsError="true"
Condition=" '$(OS)' == 'Windows_NT' " />
</Target>
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputFilename ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Net" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
OutputFilename = Path.GetFullPath(OutputFilename);
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
</Project>

3
ImageProcessor.sln

@ -9,6 +9,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Tests", "tes
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{3D5BCCE2-A7EB-4453-9A86-A9C55CCFDCDF}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
.nuget\NuGet.targets = .nuget\NuGet.targets
.nuget\packages.config = .nuget\packages.config
EndProjectSection
EndProject

34
src/ImageProcessor/Colors/Color.cs

@ -12,7 +12,6 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices;
/// <summary>
@ -222,6 +221,29 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCrColor"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="YCbCrColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(YCbCrColor color)
{
float y = color.Y;
float cb = color.Cb - 128;
float cr = color.Cr - 128;
byte r = Convert.ToByte((y + (1.402 * cr)).Clamp(0, 255));
byte g = Convert.ToByte((y - (0.34414 * cb) - (0.71414 * cr)).Clamp(0, 255));
byte b = Convert.ToByte((y + (1.772 * cb)).Clamp(0, 255));
return new Color(b, g, r, 255);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
@ -260,10 +282,12 @@ namespace ImageProcessor
/// </returns>
public override string ToString()
{
return "{B=" + this.B.ToString(CultureInfo.CurrentCulture)
+ ",G=" + this.G.ToString(CultureInfo.CurrentCulture)
+ ",R=" + this.R.ToString(CultureInfo.CurrentCulture)
+ ",A=" + this.A.ToString(CultureInfo.CurrentCulture) + "}";
if (this.IsEmpty)
{
return "Color [Empty]";
}
return string.Format("Color [ B={0}, G={1}, R={2}, A={3} ]", this.B, this.G, this.R, this.A);
}
/// <summary>

223
src/ImageProcessor/Colors/YCbCrColor.cs

@ -0,0 +1,223 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="YCbCrColor.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents an YCbCr (luminance, chroma, chroma) color conforming to the
// ITU-R BT.601 standard used in digital imaging systems.
// <see href="http://en.wikipedia.org/wiki/YCbCr" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
using System.ComponentModel;
/// <summary>
/// Represents an YCbCr (luminance, chroma, chroma) color conforming to the
/// ITU-R BT.601 standard used in digital imaging systems.
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
/// </summary>
public struct YCbCrColor : IEquatable<YCbCrColor>
{
/// <summary>
/// Represents a <see cref="YCbCrColor"/> that has Y, Cb, and Cr values set to zero.
/// </summary>
public static readonly YCbCrColor Empty = new YCbCrColor();
/// <summary>
/// Holds the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Y;
/// <summary>
/// Holds the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cb;
/// <summary>
/// Holds the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cr;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Initializes a new instance of the <see cref="YCbCrColor"/> struct.
/// </summary>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
public YCbCrColor(float y, float cb, float cr)
{
this.Y = y.Clamp(0, 255);
this.Cb = cb.Clamp(0, 255);
this.Cr = cr.Clamp(0, 255);
}
/// <summary>
/// Gets a value indicating whether this <see cref="YCbCrColor"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty
{
get
{
return Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.Cb) < Epsilon
&& Math.Abs(this.Cr) < Epsilon;
}
}
/// <summary>
/// Compares two <see cref="YCbCrColor"/> objects. The result specifies whether the values
/// of the <see cref="YCbCrColor.Y"/>, <see cref="YCbCrColor.Cb"/>, and <see cref="YCbCrColor.Cr"/>
/// properties of the two <see cref="YCbCrColor"/> objects are equal.
/// </summary>
/// <param name="left">
/// The <see cref="YCbCrColor"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="YCbCrColor"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(YCbCrColor left, YCbCrColor right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="YCbCrColor"/> objects. The result specifies whether the values
/// of the <see cref="YCbCrColor.Y"/>, <see cref="YCbCrColor.Cb"/>, and <see cref="YCbCrColor.Cr"/>
/// properties of the two <see cref="YCbCrColor"/> objects are unequal.
/// </summary>
/// <param name="left">
/// The <see cref="YCbCrColor"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="YCbCrColor"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(YCbCrColor left, YCbCrColor right)
{
return !left.Equals(right);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="YCbCrColor"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCrColor"/>.
/// </returns>
public static implicit operator YCbCrColor(Color color)
{
byte r = color.R;
byte g = color.G;
byte b = color.B;
float y = (float)((0.299 * r) + (0.587 * g) + (0.114 * b));
float cb = 128 + (float)((-0.168736 * r) - (0.331264 * g) + (0.5 * b));
float cr = 128 + (float)((0.5 * r) - (0.418688 * g) - (0.081312 * b));
return new YCbCrColor(y, cb, cr);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is YCbCrColor)
{
YCbCrColor color = (YCbCrColor)obj;
return Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.Cb - color.Cb) < Epsilon
&& Math.Abs(this.Cr - color.Cr) < Epsilon;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString()
{
if (this.IsEmpty)
{
return "YCbCrColor [Empty]";
}
return string.Format("YCbCrColor [ Y={0:#0.##}, Cb={1:#0.##}, Cr={2:#0.##}]", this.Y, this.Cb, this.Cr);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(YCbCrColor other)
{
return this.Y.Equals(other.Y)
&& this.Cb.Equals(other.Cb)
&& this.Cr.Equals(other.Cr);
}
/// <summary>
/// Returns the hash code for the given instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(YCbCrColor color)
{
unchecked
{
int hashCode = color.Y.GetHashCode();
hashCode = (hashCode * 397) ^ color.Cb.GetHashCode();
hashCode = (hashCode * 397) ^ color.Cr.GetHashCode();
return hashCode;
}
}
}
}

25
src/ImageProcessor/Common/Extensions/ComparableExtensions.cs

@ -32,5 +32,30 @@ namespace ImageProcessor
{
return (value.CompareTo(min) > 0) && (value.CompareTo(max) < 0);
}
/// <summary>
/// Restricts a value to be within a specified range.
/// </summary>
/// <param name="value">The The value to clamp.</param>
/// <param name="min">The minimum value. If value is less than min, min will be returned.</param>
/// <param name="max">The maximum value. If value is greater than max, max will be returned.</param>
/// <typeparam name="T">The <see cref="System.Type"/> to clamp.</typeparam>
/// <returns>
/// The <see cref="IComparable{T}"/> representing the clamped value.
/// </returns>
public static T Clamp<T>(this T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0)
{
return min;
}
if (value.CompareTo(max) > 0)
{
return max;
}
return value;
}
}
}

42
src/ImageProcessor/Common/Helpers/Guard.cs

@ -95,6 +95,27 @@ namespace ImageProcessor
}
}
/// <summary>
/// Verifies that the specified value is less than or equal to a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
public static void LessEquals<TValue>(TValue value, TValue max, string parameterName) where TValue : IComparable<TValue>
{
if (value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
string.Format(CultureInfo.CurrentCulture, "Value must be less than or equal to {0}", max));
}
}
/// <summary>
/// Verifies that the specified value is greater than a minimum value
/// and throws an exception if it is not.
@ -115,5 +136,26 @@ namespace ImageProcessor
string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", min));
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
public static void GreaterEquals<TValue>(TValue value, TValue min, string parameterName) where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
string.Format(CultureInfo.CurrentCulture, "Value must be greater than or equal to {0}", min));
}
}
}
}

13
src/ImageProcessor/Formats/Gif/GifDecoder.cs

@ -165,11 +165,12 @@
if (_descriptor.Width > ImageBase.MaxWidth || _descriptor.Height > ImageBase.MaxHeight)
{
throw new ArgumentOutOfRangeException(
string.Format("The input gif '{0}x{1}' is bigger then the max allowed size '{2}x{3}'",
_descriptor.Width,
_descriptor.Height,
ImageBase.MaxWidth,
ImageBase.MaxHeight));
string.Format(
"The input gif '{0}x{1}' is bigger then the max allowed size '{2}x{3}'",
_descriptor.Width,
_descriptor.Height,
ImageBase.MaxWidth,
ImageBase.MaxHeight));
}
}
@ -341,7 +342,7 @@
if (_graphicsControl != null && _graphicsControl.DelayTime > 0)
{
_image.DelayTime = _graphicsControl.DelayTime;
_image.FrameDelay = _graphicsControl.DelayTime;
}
}
else

4
src/ImageProcessor/Formats/Png/PngDecoderCore.cs

@ -188,8 +188,8 @@ namespace ImageProcessor.Formats
Array.Reverse(data, 0, 4);
Array.Reverse(data, 4, 4);
this.currentImage.DensityX = BitConverter.ToInt32(data, 0) / 39.3700787d;
this.currentImage.DensityY = BitConverter.ToInt32(data, 4) / 39.3700787d;
this.currentImage.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d;
this.currentImage.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d;
}
/// <summary>

6
src/ImageProcessor/Formats/Png/PngEncoder.cs

@ -203,10 +203,10 @@ namespace ImageProcessor.Formats
private void WritePhysicalChunk(Stream stream, ImageBase imageBase)
{
Image image = imageBase as Image;
if (image != null && image.DensityX > 0 && image.DensityY > 0)
if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0)
{
int dpmX = (int)Math.Round(image.DensityX * 39.3700787d);
int dpmY = (int)Math.Round(image.DensityY * 39.3700787d);
int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787d);
int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787d);
byte[] chunkData = new byte[9];

294
src/ImageProcessor/Image.cs

@ -1,4 +1,15 @@
namespace ImageProcessor
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Image.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Image class which stores the pixels and provides common functionality
// such as loading images from files and streams or operation like resizing or cropping.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
using System.Collections.Generic;
@ -10,87 +21,172 @@
using ImageProcessor.Formats;
/// <summary>
/// Image class with stores the pixels and provides common functionality
/// such as loading images from files and streams or operation like resizing or cutting.
/// Image class which stores the pixels and provides common functionality
/// such as loading images from files and streams or operation like resizing or cropping.
/// </summary>
/// <remarks>The image data is alway stored in RGBA format, where the red, the blue, the
/// alpha values are simple bytes.</remarks>
[DebuggerDisplay("Image: {PixelWidth}x{PixelHeight}")]
/// <remarks>
/// The image data is always stored in BGRA format, where the blue, green, red, and
/// alpha values are simple bytes.
/// </remarks>
[DebuggerDisplay("Image: {Width}x{Height}")]
public class Image : ImageBase
{
#region Constants
/// <summary>
/// The default density value (dots per inch) in x direction. The default value is 75 dots per inch.
/// The default horizontal resolution value (dots per inch) in x direction.
/// The default value is 96 dots per inch.
/// </summary>
public const double DefaultDensityX = 75;
public const double DefaultHorizontalResolution = 96;
/// <summary>
/// The default density value (dots per inch) in y direction. The default value is 75 dots per inch.
/// The default vertical resolution value (dots per inch) in y direction.
/// The default value is 96 dots per inch.
/// </summary>
public const double DefaultDensityY = 75;
public const double DefaultVerticalResolution = 96;
private static readonly Lazy<List<IImageDecoder>> defaultDecoders = new Lazy<List<IImageDecoder>>(() => new List<IImageDecoder>
/// <summary>
/// The default collection of <see cref="IImageDecoder"/>.
/// </summary>
private static readonly Lazy<List<IImageDecoder>> DefaultDecoders =
new Lazy<List<IImageDecoder>>(() => new List<IImageDecoder>
{
//new BmpDecoder(),
//new JpegDecoder(),
//new PngDecoder(),
new GifDecoder(),
// new BmpDecoder(),
// new JpegDecoder(),
new PngDecoder(),
// new GifDecoder(),
});
private static readonly Lazy<List<IImageEncoder>> defaultEncoders = new Lazy<List<IImageEncoder>>(() => new List<IImageEncoder>
/// <summary>
/// The default collection of <see cref="IImageEncoder"/>.
/// </summary>
private static readonly Lazy<List<IImageEncoder>> DefaultEncoders =
new Lazy<List<IImageEncoder>>(() => new List<IImageEncoder>
{
//new BmpEncoder(),
//new JpegEncoder(),
//new PngEncoder(),
// new BmpEncoder(),
// new JpegEncoder(),
new PngEncoder(),
});
/// <summary>
/// Gets a list of default decoders.
/// The collection of image frames.
/// </summary>
public static IList<IImageDecoder> Decoders
private readonly IList<ImageFrame> frames = new List<ImageFrame>();
/// <summary>
/// The collection of image properties.
/// </summary>
private readonly IList<ImageProperty> properties = new List<ImageProperty>();
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
public Image()
{
get { return defaultDecoders.Value; }
this.HorizontalResolution = DefaultHorizontalResolution;
this.VerticalResolution = DefaultVerticalResolution;
}
/// <summary>
/// Gets a list of default encoders.
/// Initializes a new instance of the <see cref="Image"/> class
/// with the height and the width of the image.
/// </summary>
public static IList<IImageEncoder> Encoders
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
public Image(int width, int height)
: base(width, height)
{
get { return defaultEncoders.Value; }
this.HorizontalResolution = DefaultHorizontalResolution;
this.VerticalResolution = DefaultVerticalResolution;
}
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class
/// by making a copy from another image.
/// </summary>
/// <param name="other">The other image, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null
/// (Nothing in Visual Basic).</exception>
public Image(Image other)
: base(other)
{
Guard.NotNull(other, "other", "Other image cannot be null.");
foreach (ImageFrame frame in other.Frames)
{
if (frame != null)
{
this.Frames.Add(new ImageFrame(frame));
}
}
this.HorizontalResolution = DefaultHorizontalResolution;
this.VerticalResolution = DefaultVerticalResolution;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
public Image(Stream stream)
{
Guard.NotNull(stream, "stream");
this.Load(stream, Decoders);
}
#region Fields
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <param name="decoders">
/// The collection of <see cref="IImageDecoder"/>.
/// </param>
public Image(Stream stream, params IImageDecoder[] decoders)
{
Guard.NotNull(stream, "stream");
this.Load(stream, decoders);
}
private readonly object _lockObject = new object();
/// <summary>
/// Gets a list of default decoders.
/// </summary>
public static IList<IImageDecoder> Decoders
{
get { return DefaultDecoders.Value; }
}
#endregion
/// <summary>
/// Gets a list of default encoders.
/// </summary>
public static IList<IImageEncoder> Encoders
{
get { return DefaultEncoders.Value; }
}
/// <summary>
/// Gets or sets the frame delay.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// This field may be used in conjunction with the User Input Flag field.
/// </summary>
public int? DelayTime { get; set; }
#region Properties
public int? FrameDelay { get; set; }
/// <summary>
/// Gets or sets the resolution of the image in x direction. It is defined as
/// Gets or sets the resolution of the image in x- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in x direction.</value>
public double DensityX { get; set; }
/// <value>The density of the image in x- direction.</value>
public double HorizontalResolution { get; set; }
/// <summary>
/// Gets or sets the resolution of the image in y direction. It is defined as
/// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in y direction.</value>
public double DensityY { get; set; }
/// <value>The density of the image in y- direction.</value>
public double VerticalResolution { get; set; }
/// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image
@ -102,14 +198,14 @@
{
get
{
double densityX = DensityX;
double resolution = this.HorizontalResolution;
if (densityX <= 0)
if (resolution <= 0)
{
densityX = DefaultDensityX;
resolution = DefaultHorizontalResolution;
}
return Width / densityX;
return this.Width / resolution;
}
}
@ -123,14 +219,14 @@
{
get
{
double densityY = DensityY;
double resolution = this.VerticalResolution;
if (densityY <= 0)
if (resolution <= 0)
{
densityY = DefaultDensityY;
resolution = DefaultVerticalResolution;
}
return Height / densityY;
return this.Height / resolution;
}
}
@ -142,109 +238,39 @@
/// </value>
public bool IsAnimated
{
get { return _frames.Count > 0; }
get { return this.frames.Count > 0; }
}
private IList<ImageFrame> _frames = new List<ImageFrame>();
/// <summary>
/// Get the other frames for the animation.
/// Gets the other frames for the animation.
/// </summary>
/// <value>The list of frame images.</value>
public IList<ImageFrame> Frames
{
get { return _frames; }
get { return this.frames; }
}
private IList<ImageProperty> _properties = new List<ImageProperty>();
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties
{
get { return _properties; }
get { return this.properties; }
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class
/// with the height and the width of the image.
/// Loads the image from the given stream.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
public Image(int width, int height)
: base(width, height)
{
DensityX = DefaultDensityX;
DensityY = DefaultDensityY;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class
/// by making a copy from another image.
/// </summary>
/// <param name="other">The other image, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null
/// (Nothing in Visual Basic).</exception>
public Image(Image other)
: base(other)
{
if (other == null) throw new ArgumentNullException("Other image cannot be null.");
foreach (ImageFrame frame in other.Frames)
{
if (frame != null)
{
Frames.Add(new ImageFrame(frame));
}
}
DensityX = DefaultDensityX;
DensityY = DefaultDensityY;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
public Image()
{
DensityX = DefaultDensityX;
DensityY = DefaultDensityY;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
public Image(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
Load(stream, Decoders);
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
public Image(Stream stream, params IImageDecoder[] decoders)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
Load(stream, decoders);
}
#endregion Constructors
#region Methods
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <param name="decoders">
/// The collection of <see cref="IImageDecoder"/>.
/// </param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
private void Load(Stream stream, IList<IImageDecoder> decoders)
{
try
@ -269,7 +295,7 @@
stream.Read(header, 0, maxHeaderSize);
stream.Position = 0;
var decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header));
IImageDecoder decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header));
if (decoder != null)
{
decoder.Decode(this, stream);
@ -293,7 +319,5 @@
stream.Dispose();
}
}
#endregion Methods
}
}

18
src/ImageProcessor/ImageBase.cs

@ -17,7 +17,7 @@ namespace ImageProcessor
/// The base class of all images. Encapsulates the basic properties and methods
/// required to manipulate images.
/// </summary>
public abstract class ImageBase
public abstract class ImageBase
{
/// <summary>
/// The maximum allowable width in pixels.
@ -50,15 +50,8 @@ namespace ImageProcessor
/// </exception>
protected ImageBase(int width, int height)
{
if (width <= 0)
{
throw new ArgumentOutOfRangeException("width", "Width must be greater than or equals than zero.");
}
if (height <= 0)
{
throw new ArgumentOutOfRangeException("height", "Height must be greater than or equal than zero.");
}
Guard.GreaterThan(width, 0, "width");
Guard.GreaterThan(height, 0, "height");
this.Width = width;
this.Height = height;
@ -77,10 +70,7 @@ namespace ImageProcessor
/// </exception>
protected ImageBase(ImageBase other)
{
if (other == null)
{
throw new ArgumentNullException("other", "Other image cannot be null.");
}
Guard.NotNull(other, "other", "Other image cannot be null.");
byte[] pixels = other.Pixels;

11
src/ImageProcessor/ImageProcessor.csproj

@ -15,6 +15,8 @@
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile78</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -36,8 +38,10 @@
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<Folder Include="Filters\" />
<Folder Include="Formats\Jpg\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Colors\YCbCrColor.cs" />
<Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" />
<Compile Include="Common\Helpers\Utils.cs" />
@ -82,6 +86,13 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

1
src/ImageProcessor/ImageProcessor.csproj.DotSettings

@ -6,5 +6,6 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Chelpers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=encoders_005Cgif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cgif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cjpg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cpng/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=numerics/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

2
src/ImageProcessor/Settings.StyleCop

@ -1,6 +1,8 @@
<StyleCopSettings Version="105">
<GlobalSettings>
<CollectionProperty Name="RecognizedWords">
<Value>cb</Value>
<Value>cr</Value>
<Value>EX</Value>
<Value>png</Value>
<Value>scanline</Value>

89
tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

@ -0,0 +1,89 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ColorConversionTests.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Test conversion between the various color structs.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Tests
{
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Xunit;
/// <summary>
/// Test conversion between the various color structs.
/// </summary>
public class ColorConversionTests
{
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="YCbCrColor"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
public void ColorToYCbCrColor()
{
// White
Color color = new Color(255, 255, 255, 255);
YCbCrColor yCbCrColor = color;
Assert.Equal(255, yCbCrColor.Y);
Assert.Equal(128, yCbCrColor.Cb);
Assert.Equal(128, yCbCrColor.Cr);
// Black
Color color2 = new Color(0, 0, 0, 255);
YCbCrColor yCbCrColor2 = color2;
Assert.Equal(0, yCbCrColor2.Y);
Assert.Equal(128, yCbCrColor2.Cb);
Assert.Equal(128, yCbCrColor2.Cr);
// Grey
Color color3 = new Color(128, 128, 128, 255);
YCbCrColor yCbCrColor3 = color3;
Assert.Equal(128, yCbCrColor3.Y);
Assert.Equal(128, yCbCrColor3.Cb);
Assert.Equal(128, yCbCrColor3.Cr);
}
/// <summary>
/// Tests the implicit conversion from <see cref="YCbCrColor"/> to <see cref="Color"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
public void YCbCrColorToColor()
{
// White
YCbCrColor yCbCrColor = new YCbCrColor(255, 128, 128);
Color color = yCbCrColor;
Assert.Equal(255, color.B);
Assert.Equal(255, color.G);
Assert.Equal(255, color.R);
Assert.Equal(255, color.A);
// Black
YCbCrColor yCbCrColor2 = new YCbCrColor(0, 128, 128);
Color color2 = yCbCrColor2;
Assert.Equal(0, color2.B);
Assert.Equal(0, color2.G);
Assert.Equal(0, color2.R);
Assert.Equal(255, color2.A);
// Grey
YCbCrColor yCbCrColor3 = new YCbCrColor(128, 128, 128);
Color color3 = yCbCrColor3;
Assert.Equal(128, color3.B);
Assert.Equal(128, color3.G);
Assert.Equal(128, color3.R);
Assert.Equal(255, color3.A);
}
}
}

60
tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs

@ -0,0 +1,60 @@
namespace ImageProcessor.Tests
{
using System.Diagnostics;
using System.IO;
using System.Linq;
using ImageProcessor.Formats;
using Xunit;
public class EncoderDecoderTests
{
[Theory]
//[InlineData("TestImages/Car.bmp")]
//[InlineData("TestImages/Portrait.png")]
//[InlineData("TestImages/Backdrop.jpg")]
//[InlineData("TestImages/Windmill.gif")]
[InlineData("../../TestImages/Formats/Png/cmyk.png")]
public void DecodeThenEncodeImageFromStreamShouldSucceed(string filename)
{
if (!Directory.Exists("Encoded"))
{
Directory.CreateDirectory("Encoded");
}
FileStream stream = File.OpenRead(filename);
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string encodedFilename = "Encoded/" + Path.GetFileName(filename);
//if (!image.IsAnimated)
//{
using (FileStream output = File.OpenWrite(encodedFilename))
{
IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(Path.GetExtension(filename)));
encoder.Encode(image, output);
}
//}
//else
//{
// using (var output = File.OpenWrite(
// string.Format("Encoded/{ Path.GetFileNameWithoutExtension(filename) }.jpg"))
// {
// image.SaveAsJpeg(output, 40);
// }
// for (int i = 0; i < image.Frames.Count; i++)
// {
// using (var output = File.OpenWrite($"Encoded/{ i }_{ Path.GetFileNameWithoutExtension(filename) }.png"))
// {
// image.Frames[i].SaveAsPng(output);
// }
// }
//}
Trace.WriteLine(string.Format("{0} : {1}ms", filename, watch.ElapsedMilliseconds));
}
}
}

6
tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj

@ -18,6 +18,8 @@
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>a2e7ca05</NuGetPackageImportStamp>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -60,7 +62,9 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="Colors\ColorConversionTests.cs" />
<Compile Include="Colors\ColorTests.cs" />
<Compile Include="Formats\EncoderDecoderTests.cs" />
<Compile Include="Numerics\RectangleTests.cs" />
<Compile Include="Numerics\PointTests.cs" />
<Compile Include="Numerics\SizeTests.cs" />
@ -100,7 +104,9 @@
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props'))" />
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

1
tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj.DotSettings

@ -1,3 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colors/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=numerics/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

3
tests/ImageProcessor.Tests/TestImages/Formats/Png/cmyk.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2e17ba8fac1fd6247ff839ab7e21d715c96b3588d5f4fdb835dbe8c937dbf79d
size 37678

1
tests/ImageProcessor.Tests/packages.config

@ -5,4 +5,5 @@
<package id="xunit.assert" version="2.0.0" targetFramework="net45" />
<package id="xunit.core" version="2.0.0" targetFramework="net45" />
<package id="xunit.extensibility.core" version="2.0.0" targetFramework="net45" />
<package id="xunit.runner.console" version="2.0.0" />
</packages>
Loading…
Cancel
Save