From 5e0d0de329a10e54ef8f3671fca17a67142e5458 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 27 Oct 2015 12:58:11 +1100 Subject: [PATCH] Add ColorMatrix functionality Former-commit-id: 741ed5325553c7e521375e2b6168cfd345fe69d5 Former-commit-id: f290ab071672406c5f3d846cedea2e7c280329c0 Former-commit-id: 1ca475830cd7732a17fb3f407418189aea692e8e --- .../Filters/ColorMatrix/BlackWhite.cs | 35 +++ .../Filters/ColorMatrix/ColorMatrix.cs | 273 ++++++++++++++++++ .../Filters/ColorMatrix/ColorMatrixFilter.cs | 101 +++++++ .../Filters/ColorMatrix/GreyscaleBt601.cs | 35 +++ .../Filters/ColorMatrix/GreyscaleBt709.cs | 35 +++ .../Filters/ColorMatrix/Invert.cs | 35 +++ .../Filters/ColorMatrix/Lomograph.cs | 35 +++ .../Filters/ColorMatrix/Polaroid.cs | 35 +++ .../Filters/ColorMatrix/Sepia.cs | 35 +++ .../Filters/ImageFilterExtensions.cs | 23 ++ .../Formats/Gif/Quantizer/Quantizer.cs | 2 +- src/ImageProcessor/ImageProcessor.csproj | 18 +- .../ImageProcessor.csproj.DotSettings | 1 + src/ImageProcessor/Settings.StyleCop | 6 + src/ImageProcessor/packages.config | 2 +- .../Processors/Filters/FilterTests.cs | 7 + 16 files changed, 674 insertions(+), 4 deletions(-) create mode 100644 src/ImageProcessor/Filters/ColorMatrix/BlackWhite.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/ColorMatrix.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/Invert.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs create mode 100644 src/ImageProcessor/Filters/ColorMatrix/Sepia.cs diff --git a/src/ImageProcessor/Filters/ColorMatrix/BlackWhite.cs b/src/ImageProcessor/Filters/ColorMatrix/BlackWhite.cs new file mode 100644 index 000000000..8f63fbaea --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/BlackWhite.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Converts the colors of the image to their black and white equivalent. + /// + public class BlackWhite : ColorMatrixFilter + { + /// + /// The BlackWhite matrix. + /// TODO: Calculate a matrix that works in the linear color space. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new[] { 1.5f, 1.5f, 1.5f, 0, 0 }, + new[] { 1.5f, 1.5f, 1.5f, 0, 0 }, + new[] { 1.5f, 1.5f, 1.5f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { -1, -1, -1, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public BlackWhite() + : base(Matrix, false) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrix.cs b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrix.cs new file mode 100644 index 000000000..564befd88 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrix.cs @@ -0,0 +1,273 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Filters +{ + /// + /// Defines a 5 x 5 matrix that contains the coordinates for the RGBAW color space. + /// + public sealed class ColorMatrix + { + /// + /// Initializes a new instance of the class. + /// + public ColorMatrix() + { + // Setup the identity matrix by default + this.Matrix00 = 1.0f; + this.Matrix11 = 1.0f; + this.Matrix22 = 1.0f; + this.Matrix33 = 1.0f; + this.Matrix44 = 1.0f; + } + + /// + /// Initializes a new instance of the class with the + /// elements in the specified matrix. + /// + /// + /// The elements defining the new Color Matrix. + /// + public ColorMatrix(float[][] colorMatrix) + { + this.SetMatrix(colorMatrix); + } + + /// + /// Gets or sets the element at the 0th row and 0th column of this . + /// + public float Matrix00 { get; set; } + + /// + /// Gets or sets the element at the 0th row and 1st column of this . + /// + public float Matrix01 { get; set; } + + /// + /// Gets or sets the element at the 0th row and 2nd column of this . + /// + public float Matrix02 { get; set; } + + /// + /// Gets or sets the element at the 0th row and 3rd column of this . + /// + public float Matrix03 { get; set; } + + /// + /// Gets or sets the element at the 0th row and 4th column of this . + /// + public float Matrix04 { get; set; } + + /// + /// Gets or sets the element at the 1st row and 0th column of this . + /// + public float Matrix10 { get; set; } + + /// + /// Gets or sets the element at the 1st row and 1st column of this . + /// + public float Matrix11 { get; set; } + + /// + /// Gets or sets the element at the 1st row and 2nd column of this . + /// + public float Matrix12 { get; set; } + + /// + /// Gets or sets the element at the 1st row and 3rd column of this . + /// + public float Matrix13 { get; set; } + + /// + /// Gets or sets the element at the 1st row and 4th column of this . + /// + public float Matrix14 { get; set; } + + /// + /// Gets or sets the element at the 2nd row and 0th column of this . + /// + public float Matrix20 { get; set; } + + /// + /// Gets or sets the element at the 2nd row and 1st column of this . + /// + public float Matrix21 { get; set; } + + /// + /// Gets or sets the element at the 2nd row and 2nd column of this . + /// + public float Matrix22 { get; set; } + + /// + /// Gets or sets the element at the 2nd row and 3rd column of this . + /// + public float Matrix23 { get; set; } + + /// + /// Gets or sets the element at the 2nd row and 4th column of this . + /// + public float Matrix24 { get; set; } + + /// + /// Gets or sets the element at the 3rd row and 0th column of this . + /// + public float Matrix30 { get; set; } + + /// + /// Gets or sets the element at the 3rd row and 1st column of this . + /// + public float Matrix31 { get; set; } + + /// + /// Gets or sets the element at the 3rd row and 2nd column of this . + /// + public float Matrix32 { get; set; } + + /// + /// Gets or sets the element at the 3rd row and 3rd column of this . + /// + public float Matrix33 { get; set; } + + /// + /// Gets or sets the element at the 3rd row and 4th column of this . + /// + public float Matrix34 { get; set; } + + /// + /// Gets or sets the element at the 4th row and 0th column of this . + /// + public float Matrix40 { get; set; } + + /// + /// Gets or sets the element at the 4th row and 1st column of this . + /// + public float Matrix41 { get; set; } + + /// + /// Gets or sets the element at the 4th row and 2nd column of this . + /// + public float Matrix42 { get; set; } + + /// + /// Gets or sets the element at the 4th row and 3rd column of this . + /// + public float Matrix43 { get; set; } + + /// + /// Gets or sets the element at the 4th row and 4th column of this . + /// + public float Matrix44 { get; set; } + + /// + /// Gets or sets the value of the specified element of this . + /// + /// + /// The row index. + /// + /// + /// The column index. + /// + /// + /// The . + /// + public float this[int row, int column] + { + get + { + return this.GetMatrix()[row][column]; + } + + set + { + float[][] tempMatrix = this.GetMatrix(); + + tempMatrix[row][column] = value; + + this.SetMatrix(tempMatrix); + } + } + + /// + /// Sets the values of this to the values contained within the elements. + /// + /// + /// The new color matrix. + /// + internal void SetMatrix(float[][] colorMatrix) + { + this.Matrix00 = colorMatrix[0][0]; + this.Matrix01 = colorMatrix[0][1]; + this.Matrix02 = colorMatrix[0][2]; + this.Matrix03 = colorMatrix[0][3]; + this.Matrix04 = colorMatrix[0][4]; + this.Matrix10 = colorMatrix[1][0]; + this.Matrix11 = colorMatrix[1][1]; + this.Matrix12 = colorMatrix[1][2]; + this.Matrix13 = colorMatrix[1][3]; + this.Matrix14 = colorMatrix[1][4]; + this.Matrix20 = colorMatrix[2][0]; + this.Matrix21 = colorMatrix[2][1]; + this.Matrix22 = colorMatrix[2][2]; + this.Matrix23 = colorMatrix[2][3]; + this.Matrix24 = colorMatrix[2][4]; + this.Matrix30 = colorMatrix[3][0]; + this.Matrix31 = colorMatrix[3][1]; + this.Matrix32 = colorMatrix[3][2]; + this.Matrix33 = colorMatrix[3][3]; + this.Matrix34 = colorMatrix[3][4]; + this.Matrix40 = colorMatrix[4][0]; + this.Matrix41 = colorMatrix[4][1]; + this.Matrix42 = colorMatrix[4][2]; + this.Matrix43 = colorMatrix[4][3]; + this.Matrix44 = colorMatrix[4][4]; + } + + /// + /// Gets this . + /// + /// + /// The . + /// + internal float[][] GetMatrix() + { + float[][] returnMatrix = new float[5][]; + + for (int i = 0; i < 5; i++) + { + returnMatrix[i] = new float[5]; + } + + returnMatrix[0][0] = this.Matrix00; + returnMatrix[0][1] = this.Matrix01; + returnMatrix[0][2] = this.Matrix02; + returnMatrix[0][3] = this.Matrix03; + returnMatrix[0][4] = this.Matrix04; + returnMatrix[1][0] = this.Matrix10; + returnMatrix[1][1] = this.Matrix11; + returnMatrix[1][2] = this.Matrix12; + returnMatrix[1][3] = this.Matrix13; + returnMatrix[1][4] = this.Matrix14; + returnMatrix[2][0] = this.Matrix20; + returnMatrix[2][1] = this.Matrix21; + returnMatrix[2][2] = this.Matrix22; + returnMatrix[2][3] = this.Matrix23; + returnMatrix[2][4] = this.Matrix24; + returnMatrix[3][0] = this.Matrix30; + returnMatrix[3][1] = this.Matrix31; + returnMatrix[3][2] = this.Matrix32; + returnMatrix[3][3] = this.Matrix33; + returnMatrix[3][4] = this.Matrix34; + returnMatrix[4][0] = this.Matrix40; + returnMatrix[4][1] = this.Matrix41; + returnMatrix[4][2] = this.Matrix42; + returnMatrix[4][3] = this.Matrix43; + returnMatrix[4][4] = this.Matrix44; + + return returnMatrix; + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs new file mode 100644 index 000000000..9442ea558 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs @@ -0,0 +1,101 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The color matrix filter. + /// + public class ColorMatrixFilter : ParallelImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The matrix to apply. + /// Whether to gamma adjust the colors before applying the matrix. + public ColorMatrixFilter(ColorMatrix matrix, bool gammaAdjust) + { + this.Value = matrix; + this.GammaAdjust = gammaAdjust; + } + + /// + /// Gets the matrix value. + /// + public ColorMatrix Value { get; } + + /// + /// Gets a value indicating whether to gamma adjust the colors before applying the matrix. + /// + public bool GammaAdjust { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + ColorMatrix matrix = this.Value; + Bgra previousColor = source[0, 0]; + Bgra pixelValue = this.ApplyMatrix(previousColor, matrix); + + for (int y = startY; y < endY; y++) + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + Bgra sourceColor = source[x, y]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (sourceColor != previousColor) + { + // Perform the operation on the pixel. + pixelValue = this.ApplyMatrix(sourceColor, matrix); + + // And setup the previous pointer + previousColor = sourceColor; + } + + target[x, y] = pixelValue; + } + } + } + } + + /// + /// Applies the color matrix against the given color. + /// + /// The source color. + /// The matrix. + /// + /// The . + /// + private Bgra ApplyMatrix(Bgra sourceColor, ColorMatrix matrix) + { + bool gamma = this.GammaAdjust; + + if (gamma) + { + sourceColor = PixelOperations.ToLinear(sourceColor); + } + + int sr = sourceColor.R; + int sg = sourceColor.G; + int sb = sourceColor.B; + int sa = sourceColor.A; + + // TODO: Investigate RGBAW + byte r = ((sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + (255f * matrix.Matrix40)).ToByte(); + byte g = ((sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + (255f * matrix.Matrix41)).ToByte(); + byte b = ((sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + (255f * matrix.Matrix42)).ToByte(); + byte a = ((sr * matrix.Matrix03) + (sg * matrix.Matrix13) + (sb * matrix.Matrix23) + (sa * matrix.Matrix33) + (255f * matrix.Matrix43)).ToByte(); + + return gamma ? PixelOperations.ToSrgb(new Bgra(b, g, r, a)) : new Bgra(b, g, r, a); + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs new file mode 100644 index 000000000..749d7adc4 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt601.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Converts the colors of the image to greyscale applying the formula as specified by + /// ITU-R Recommendation BT.601 . + /// + public class GreyscaleBt601 : ColorMatrixFilter + { + /// + /// The inversion matrix. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new float[] { 0.299f, 0.299f, 0.299f, 0, 0 }, + new float[] { 0.587f, 0.587f, 0.587f, 0, 0 }, + new float[] { 0.114f, 0.114f, 0.114f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public GreyscaleBt601() + : base(Matrix, true) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs new file mode 100644 index 000000000..4dc074ffd --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/GreyscaleBt709.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Converts the colors of the image to greyscale applying the formula as specified by + /// ITU-R Recommendation BT.709 . + /// + public class GreyscaleBt709 : ColorMatrixFilter + { + /// + /// The inversion matrix. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new float[] { 0.2126f, 0.2126f, 0.2126f, 0, 0 }, + new float[] { 0.7152f, 0.7152f, 0.7152f, 0, 0 }, + new float[] { 0.0722f, 0.0722f, 0.0722f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public GreyscaleBt709() + : base(Matrix, true) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/Invert.cs b/src/ImageProcessor/Filters/ColorMatrix/Invert.cs new file mode 100644 index 000000000..24c8f52a0 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/Invert.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Inverts the colors of the image. + /// + public class Invert : ColorMatrixFilter + { + /// + /// The inversion matrix. + /// TODO: With gamma adjustment enabled this leaves the image too bright. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new float[] { -1, 0, 0, 0, 0 }, + new float[] { 0, -1, 0, 0, 0 }, + new float[] { 0, 0, -1, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 1, 1, 1, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public Invert() + : base(Matrix, false) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs b/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs new file mode 100644 index 000000000..31e4c5d32 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/Lomograph.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Converts the colors of the image recreating an old Lomograph effect. + /// + public class Lomograph : ColorMatrixFilter + { + /// + /// The Lomograph matrix. Purely artistic in composition. + /// TODO: Calculate a matrix that works in the linear color space. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new[] { 1.50f, 0, 0, 0, 0 }, + new[] { 0, 1.45f, 0, 0, 0 }, + new[] { 0, 0, 1.09f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new[] { -0.10f, 0.05f, -0.08f, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public Lomograph() + : base(Matrix, false) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs b/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs new file mode 100644 index 000000000..b86173a59 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Converts the colors of the image recreating an old Polaroid effect. + /// + public class Polaroid : ColorMatrixFilter + { + /// + /// The Polaroid matrix. Purely artistic in composition. + /// TODO: Calculate a matrix that works in the linear color space. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new[] { 1.638f, -0.062f, -0.262f, 0, 0 }, + new[] { -0.122f, 1.378f, -0.122f, 0, 0 }, + new[] { 1.016f, -0.016f, 1.383f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new[] { 0.06f, -0.05f, -0.05f, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public Polaroid() + : base(Matrix, false) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs new file mode 100644 index 000000000..7d622ad53 --- /dev/null +++ b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs @@ -0,0 +1,35 @@ +// +// Copyright © James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// Converts the colors of the image to their sepia equivalent recreating an old photo effect. + /// + public class Sepia : ColorMatrixFilter + { + /// + /// The sepia matrix. + /// TODO: Calculate a matrix that works in the linear color space. + /// + private static readonly ColorMatrix Matrix = new ColorMatrix( + new[] + { + new[] { .393f, .349f, .272f, 0, 0 }, + new[] { .769f, .686f, .534f, 0, 0 }, + new[] { .189f, .168f, .131f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + + /// + /// Initializes a new instance of the class. + /// + public Sepia() + : base(Matrix, false) + { + } + } +} diff --git a/src/ImageProcessor/Filters/ImageFilterExtensions.cs b/src/ImageProcessor/Filters/ImageFilterExtensions.cs index 351a1921e..4aa511e36 100644 --- a/src/ImageProcessor/Filters/ImageFilterExtensions.cs +++ b/src/ImageProcessor/Filters/ImageFilterExtensions.cs @@ -59,5 +59,28 @@ namespace ImageProcessor.Filters { return source.Process(sourceRectangle, new Alpha(percent)); } + + /// + /// Alters the alpha component of the image. + /// + /// The image this method extends. + /// The . + public static Image Invert(this Image source) + { + return Invert(source, source.Bounds); + } + + /// + /// Alters the alpha component of the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The . + public static Image Invert(this Image source, Rectangle sourceRectangle) + { + return source.Process(sourceRectangle, new Invert()); + } } } diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs index cfbd23223..99ce8643e 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs @@ -74,7 +74,7 @@ namespace ImageProcessor.Formats // Loop through each row for (int y = 0; y < height; y++) { - // And loop through each xumn + // And loop through each column for (int x = 0; x < width; x++) { // Now I have the pixel, call the FirstPassQuantize function... diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index c1db48be3..a35f2f0a3 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -17,6 +17,7 @@ v4.5 ..\..\ true + cfa9b76b true @@ -27,6 +28,7 @@ prompt 4 bin\Debug\ImageProcessor.XML + default pdbonly @@ -44,6 +46,15 @@ + + + + + + + + + @@ -210,21 +221,24 @@ + - - + + 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}. + +