diff --git a/README.md b/README.md
index 130b3d6f0..1432317c1 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] Size
- [x] Point
- [x] Ellipse
-- Resampling algorithms. (Optional gamma correction, Performance improvements?)
+- Resampling algorithms. (Optional gamma correction, resize modes, Performance improvements?)
- [x] Box
- [x] Bicubic
- [x] Lanczos3
@@ -86,13 +86,18 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] Spline
- [x] Triangle
- [x] Welch
+- Padding
+ - [x] Pad
+ - [x] ResizeMode.Pad
+ - [x] ResizeMode.BoxPad
- Cropping
- [x] Rectangular Crop
- [ ] Elliptical Crop
- [x] Entropy Crop
+ - [x] ResizeMode.Crop
- Rotation/Skew
- [x] Flip (90, 270, FlipType etc)
- - [x] Rotate by angle and center point.
+ - [x] Rotate by angle and center point (Expandable canvas).
- [x] Skew by x/y angles and center point.
- ColorMatrix operations (Uses Matrix4x4)
- [x] BlackWhite
diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
index 662a41f1b..ab4394feb 100644
--- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
+++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
@@ -112,15 +112,13 @@ namespace ImageProcessorCore
///
/// Returns the given degrees converted to radians.
///
- ///
- /// The angle in degrees.
- ///
+ /// The angle in degrees.
///
- /// The representing the degree as radians.
+ /// The representing the degree as radians.
///
- public static double DegreesToRadians(double angleInDegrees)
+ public static float DegreesToRadians(float degrees)
{
- return angleInDegrees * (PI / 180);
+ return degrees * (PI / 180);
}
///
@@ -140,6 +138,24 @@ namespace ImageProcessorCore
return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
}
+ // http://gamedev.stackexchange.com/questions/22840/create-a-rectangle-struct-to-be-rotated-and-have-a-intersects-function
+ public static Rectangle GetBoundingRotatedRectangle(Rectangle rectangle, float degrees, Point center)
+ {
+ float radians = DegreesToRadians(degrees);
+ Matrix3x2 matrix = Matrix3x2.CreateRotation(radians, center.ToVector2());
+
+ Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix);
+ Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix);
+ Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix);
+ Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix);
+
+ Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom));
+ Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom));
+
+ // TODO: minY is wrong - negative
+ return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y));
+ }
+
///
/// Calculates the new size after rotation.
///
diff --git a/src/ImageProcessorCore/Numerics/Point.cs b/src/ImageProcessorCore/Numerics/Point.cs
index 472a1c773..f1fe3c5ec 100644
--- a/src/ImageProcessorCore/Numerics/Point.cs
+++ b/src/ImageProcessorCore/Numerics/Point.cs
@@ -159,26 +159,26 @@ namespace ImageProcessorCore
}
///
- /// Rotates a point around a given a rotation matrix.
+ /// Creates a rotation matrix for the given point and angle.
///
- /// The point to rotate
- /// Rotation matrix used
- ///
- public static Point Rotate( Point point, Matrix3x2 rotation )
+ /// The origin point to rotate around
+ /// Rotation in degrees
+ /// The rotation
+ public static Matrix3x2 CreateRotation(Point origin, float degrees)
{
- return new Point( Vector2.Transform( point.backingVector, rotation ) );
+ float radians = (float)ImageMaths.DegreesToRadians(degrees);
+ return Matrix3x2.CreateRotation(radians, origin.backingVector);
}
///
- /// Creates a rotation matrix for
+ /// Rotates a point around a given a rotation matrix.
///
- /// The origin point to rotate around
- /// Rotation in degrees
- ///
- public static Matrix3x2 CreateRotatation( Point origin, float degrees )
+ /// The point to rotate
+ /// Rotation matrix used
+ /// The rotated
+ public static Point Rotate(Point point, Matrix3x2 rotation)
{
- float radians = (float)ImageMaths.DegreesToRadians( degrees );
- return Matrix3x2.CreateRotation( radians, origin.backingVector );
+ return new Point(Vector2.Transform(point.backingVector, rotation));
}
///
@@ -187,26 +187,48 @@ namespace ImageProcessorCore
/// The point to rotate
/// The center point to rotate around.
/// The angle in degrees.
- ///
+ /// The rotated
public static Point Rotate(Point point, Point origin, float degrees)
{
- float radians = (float)ImageMaths.DegreesToRadians(degrees);
- return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateRotation(radians, origin.backingVector)));
+ return new Point(Vector2.Transform(point.backingVector, CreateRotation(origin, degrees)));
}
///
- /// Skews a point around a given origin by the specified angles in degrees.
+ /// Creates a skew matrix for the given point and angle.
+ ///
+ /// The origin point to rotate around
+ /// The x-angle in degrees.
+ /// The y-angle in degrees.
+ /// The rotation
+ public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY)
+ {
+ float radiansX = (float)ImageMaths.DegreesToRadians(degreesX);
+ float radiansY = (float)ImageMaths.DegreesToRadians(degreesY);
+ return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector);
+ }
+
+ ///
+ /// Skews a point using a given a skew matrix.
///
/// The point to rotate
+ /// Rotation matrix used
+ /// The rotated
+ public static Point Skew(Point point, Matrix3x2 skew)
+ {
+ return new Point(Vector2.Transform(point.backingVector, skew));
+ }
+
+ ///
+ /// Skews a point around a given origin by the specified angles in degrees.
+ ///
+ /// The point to skew.
/// The center point to rotate around.
/// The x-angle in degrees.
/// The y-angle in degrees.
- ///
+ /// The skewed
public static Point Skew(Point point, Point origin, float degreesX, float degreesY)
{
- float radiansX = (float)ImageMaths.DegreesToRadians(degreesX);
- float radiansY = (float)ImageMaths.DegreesToRadians(degreesY);
- return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateSkew(degreesX, degreesY, origin.backingVector)));
+ return new Point(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY)));
}
///
@@ -223,8 +245,7 @@ namespace ImageProcessorCore
return "Point [ Empty ]";
}
- return
- $"Point [ X={this.X}, Y={this.Y} ]";
+ return $"Point [ X={this.X}, Y={this.Y} ]";
}
///
diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
index 71c40f6c0..45dc3c913 100644
--- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
+++ b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
@@ -42,7 +42,7 @@ namespace ImageProcessorCore.Samplers
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
-
+
if (sourceRectangle.Width < width || sourceRectangle.Height < height)
{
// If the source rectangle is smaller than the target perform a
@@ -85,6 +85,26 @@ namespace ImageProcessorCore.Samplers
}
}
+ ///
+ /// Evenly pads an image to fit the new dimensions.
+ ///
+ /// The source image to pad.
+ /// The new width.
+ /// The new height.
+ /// A delegate which is called as progress is made processing the image.
+ /// The .
+ public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
+ {
+ ResizeOptions options = new ResizeOptions
+ {
+ Size = new Size(width, height),
+ Mode = ResizeMode.BoxPad,
+ Sampler = new NearestNeighborResampler()
+ };
+
+ return Resize(source, options, progressHandler);
+ }
+
///
/// Resizes an image in accordance with the given .
///
@@ -95,7 +115,7 @@ namespace ImageProcessorCore.Samplers
/// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image
public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null)
{
- // Ensure size is populated acros both dimensions.
+ // Ensure size is populated across both dimensions.
if (options.Size.Width == 0 && options.Size.Height > 0)
{
options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height);
@@ -203,7 +223,7 @@ namespace ImageProcessorCore.Samplers
}
///
- /// Rotates an image by the given angle in degrees.
+ /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
///
/// The image to rotate.
/// The angle in degrees to perform the rotation.
@@ -211,7 +231,7 @@ namespace ImageProcessorCore.Samplers
/// The
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
{
- return Rotate(source, degrees, Rectangle.Center(source.Bounds), progressHandler);
+ return Rotate(source, degrees, Point.Empty, true, progressHandler);
}
///
@@ -219,12 +239,13 @@ namespace ImageProcessorCore.Samplers
///
/// The image to rotate.
/// The angle in degrees to perform the rotation.
- /// The center point at which to skew the image.
+ /// The center point at which to rotate the image.
+ /// Whether to expand the image to fit the rotated result.
/// A delegate which is called as progress is made processing the image.
/// The
- public static Image Rotate(this Image source, float degrees, Point center, ProgressEventHandler progressHandler = null)
+ public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null)
{
- Rotate processor = new Rotate { Angle = degrees, Center = center };
+ Rotate processor = new Rotate { Angle = degrees, Center = center, Expand = expand };
processor.OnProgress += progressHandler;
try
@@ -270,7 +291,7 @@ namespace ImageProcessorCore.Samplers
/// The
public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null)
{
- return Skew(source, degreesX, degreesY, Rectangle.Center(source.Bounds), progressHandler);
+ return Skew(source, degreesX, degreesY, Point.Empty, progressHandler);
}
///
diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs
index a9f528aad..5cbf45f35 100644
--- a/src/ImageProcessorCore/Samplers/Resize.cs
+++ b/src/ImageProcessorCore/Samplers/Resize.cs
@@ -184,6 +184,9 @@ namespace ImageProcessorCore.Samplers
{
target.ClonePixels(target.Width, target.Height, source.Pixels);
}
+
+ // Clean up
+ this.firstPass?.Dispose();
}
}
}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs
index bc10e2fdb..a14b3cee1 100644
--- a/src/ImageProcessorCore/Samplers/Rotate.cs
+++ b/src/ImageProcessorCore/Samplers/Rotate.cs
@@ -3,10 +3,9 @@
// Licensed under the Apache License, Version 2.0.
//
-using System.Numerics;
-
namespace ImageProcessorCore.Samplers
{
+ using System.Numerics;
using System.Threading.Tasks;
///
@@ -15,12 +14,20 @@ namespace ImageProcessorCore.Samplers
public class Rotate : ImageSampler
{
///
- /// The angle of rotation.
+ /// The image used for storing the first pass pixels.
+ ///
+ private Image firstPass;
+
+ ///
+ /// The angle of rotation in degrees.
///
private float angle;
+ ///
+ public override int Parallelism { get; set; } = 1;
+
///
- /// Gets or sets the angle of rotation.
+ /// Gets or sets the angle of rotation in degrees.
///
public float Angle
{
@@ -50,47 +57,84 @@ namespace ImageProcessorCore.Samplers
///
public Point Center { get; set; }
+ ///
+ /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
+ ///
+ public bool Expand { get; set; }
+
///
- protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
- int targetY = targetRectangle.Y;
- int targetBottom = targetRectangle.Bottom;
- int startX = targetRectangle.X;
- int endX = targetRectangle.Right;
- float negativeAngle = -this.angle;
- Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
+ // If we are expanding we need to pad the bounds of the source rectangle.
+ // We can use the resizer in nearest neighbor mode to do this fairly quickly.
+ if (this.Expand)
+ {
+ // First find out how the target rectangle should be.
+ Rectangle rectangle = ImageMaths.GetBoundingRotatedRectangle(source.Width, source.Height, -this.angle);
+ Rectangle rectangle2 = ImageMaths.GetBoundingRotatedRectangle(sourceRectangle, -this.angle, this.Center);
+ ResizeOptions options = new ResizeOptions
+ {
+ Size = new Size(rectangle.Width, rectangle.Height),
+ Mode = ResizeMode.BoxPad,
+ Sampler = new NearestNeighborResampler()
+ };
- // Scaling factors
- float widthFactor = source.Width / (float)target.Width;
- float heightFactor = source.Height / (float)target.Height;
+ // Get the padded bounds and resize the image.
+ Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options);
+ this.firstPass = new Image(rectangle.Width, rectangle.Height);
+ target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
+ new Resize(new NearestNeighborResampler()).Apply(this.firstPass, source, rectangle.Width, rectangle.Height, bounds, sourceRectangle);
+ }
+ else
+ {
+ // Just clone the pixels across.
+ this.firstPass = new Image(source.Width, source.Height);
+ this.firstPass.ClonePixels(source.Width, source.Height, source.Pixels);
+ }
+ }
- Matrix3x2 rotation = Point.CreateRotatation( centre, negativeAngle );
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ int targetY = this.firstPass.Bounds.Y;
+ int targetHeight = this.firstPass.Height;
+ int startX = this.firstPass.Bounds.X;
+ int endX = this.firstPass.Bounds.Right;
+ float negativeAngle = -this.angle;
+ Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center;
+ Matrix3x2 rotation = Point.CreateRotation(centre, negativeAngle);
+ // Since we are not working in parallel we use full height and width of the first pass image.
Parallel.For(
- startY,
- endY,
+ 0,
+ targetHeight,
y =>
{
- if (y >= targetY && y < targetBottom)
+ // Y coordinates of source points
+ int originY = y - targetY;
+
+ for (int x = startX; x < endX; x++)
{
- // Y coordinates of source points
- int originY = (int)((y - targetY) * heightFactor);
+ // X coordinates of source points
+ int originX = x - startX;
- for (int x = startX; x < endX; x++)
+ // Rotate at the centre point
+ Point rotated = Point.Rotate(new Point(originX, originY), rotation);
+ if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y))
{
- // X coordinates of source points
- int originX = (int)((x - startX) * widthFactor);
-
- // Rotate at the centre point
- Point rotated = Point.Rotate(new Point(originX, originY), rotation);
- if (sourceRectangle.Contains(rotated.X, rotated.Y))
- {
- target[x, y] = source[rotated.X, rotated.Y];
- }
+ target[x, y] = this.firstPass[rotated.X, rotated.Y];
}
- this.OnRowProcessed();
}
+
+ this.OnRowProcessed();
});
}
+
+ ///
+ protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ // Cleanup.
+ this.firstPass.Dispose();
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs
index 09162bff0..aec9b9bbf 100644
--- a/src/ImageProcessorCore/Samplers/RotateFlip.cs
+++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs
@@ -2,7 +2,6 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageProcessorCore.Samplers
{
using System;
@@ -43,13 +42,13 @@ namespace ImageProcessorCore.Samplers
switch (this.RotateType)
{
case RotateType.Rotate90:
- Rotate90(target, source);
+ this.Rotate90(target, source);
break;
case RotateType.Rotate180:
- Rotate180(target, source);
+ this.Rotate180(target, source);
break;
case RotateType.Rotate270:
- Rotate270(target, source);
+ this.Rotate270(target, source);
break;
default:
target.ClonePixels(target.Width, target.Height, source.Pixels);
@@ -60,10 +59,10 @@ namespace ImageProcessorCore.Samplers
{
// No default needed as we have already set the pixels.
case FlipType.Vertical:
- FlipX(target);
+ this.FlipX(target);
break;
case FlipType.Horizontal:
- FlipY(target);
+ this.FlipY(target);
break;
}
}
@@ -79,7 +78,7 @@ namespace ImageProcessorCore.Samplers
int height = source.Height;
Image temp = new Image(height, width);
- Parallel.For(0, height,
+ Parallel.For(0, height,
y =>
{
for (int x = 0; x < width; x++)
@@ -90,6 +89,7 @@ namespace ImageProcessorCore.Samplers
newY = width - newY - 1;
temp[newX, newY] = source[x, y];
}
+
this.OnRowProcessed();
});
@@ -106,7 +106,7 @@ namespace ImageProcessorCore.Samplers
int width = source.Width;
int height = source.Height;
- Parallel.For(0, height,
+ Parallel.For(0, height,
y =>
{
for (int x = 0; x < width; x++)
@@ -115,6 +115,7 @@ namespace ImageProcessorCore.Samplers
int newY = height - y - 1;
target[newX, newY] = source[x, y];
}
+
this.OnRowProcessed();
});
}
@@ -130,7 +131,7 @@ namespace ImageProcessorCore.Samplers
int height = source.Height;
Image temp = new Image(height, width);
- Parallel.For(0, height,
+ Parallel.For(0, height,
y =>
{
for (int x = 0; x < width; x++)
@@ -138,6 +139,7 @@ namespace ImageProcessorCore.Samplers
int newX = height - y - 1;
temp[newX, x] = source[x, y];
}
+
this.OnRowProcessed();
});
@@ -157,7 +159,7 @@ namespace ImageProcessorCore.Samplers
ImageBase temp = new Image(width, height);
temp.ClonePixels(width, height, target.Pixels);
- Parallel.For(0, halfHeight,
+ Parallel.For(0, halfHeight,
y =>
{
for (int x = 0; x < width; x++)
@@ -166,6 +168,7 @@ namespace ImageProcessorCore.Samplers
target[x, y] = temp[x, newY];
target[x, newY] = temp[x, y];
}
+
this.OnRowProcessed();
});
}
@@ -183,7 +186,7 @@ namespace ImageProcessorCore.Samplers
ImageBase temp = new Image(width, height);
temp.ClonePixels(width, height, target.Pixels);
- Parallel.For(0, height,
+ Parallel.For(0, height,
y =>
{
for (int x = 0; x < halfWidth; x++)
@@ -192,6 +195,7 @@ namespace ImageProcessorCore.Samplers
target[x, y] = temp[newX, y];
target[newX, y] = temp[x, y];
}
+
this.OnRowProcessed();
});
}
diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs
index 17e42833f..1d88811d1 100644
--- a/src/ImageProcessorCore/Samplers/Skew.cs
+++ b/src/ImageProcessorCore/Samplers/Skew.cs
@@ -5,6 +5,7 @@
namespace ImageProcessorCore.Samplers
{
+ using System.Numerics;
using System.Threading.Tasks;
///
@@ -89,10 +90,7 @@ namespace ImageProcessorCore.Samplers
float negativeAngleX = -this.angleX;
float negativeAngleY = -this.angleY;
Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
-
- // Scaling factors
- float widthFactor = source.Width / (float)target.Width;
- float heightFactor = source.Height / (float)target.Height;
+ Matrix3x2 skew = Point.CreateSkew(centre, negativeAngleX, negativeAngleY);
Parallel.For(
startY,
@@ -102,20 +100,21 @@ namespace ImageProcessorCore.Samplers
if (y >= targetY && y < targetBottom)
{
// Y coordinates of source points
- int originY = (int)((y - targetY) * heightFactor);
+ int originY = y - targetY;
for (int x = startX; x < endX; x++)
{
// X coordinates of source points
- int originX = (int)((x - startX) * widthFactor);
+ int originX = x - startX;
// Skew at the centre point
- Point skewed = Point.Skew(new Point(originX, originY), centre, negativeAngleX, negativeAngleY);
+ Point skewed = Point.Skew(new Point(originX, originY), skew);
if (sourceRectangle.Contains(skewed.X, skewed.Y))
{
target[x, y] = source[skewed.X, skewed.Y];
}
}
+
this.OnRowProcessed();
}
});
diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
index 2516cf62b..7d57934f0 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
@@ -76,6 +76,34 @@
}
}
+ [Fact]
+ public void ImageShouldPad()
+ {
+ if (!Directory.Exists("TestOutput/Pad"))
+ {
+ Directory.CreateDirectory("TestOutput/Pad");
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Stopwatch watch = Stopwatch.StartNew();
+
+ string filename = Path.GetFileName(file);
+
+ using (Image image = new Image(stream))
+ using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}"))
+ {
+ image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate)
+ .Save(output);
+ }
+
+ Trace.WriteLine($"{watch.ElapsedMilliseconds}ms");
+ }
+ }
+ }
+
[Theory]
[MemberData("ReSamplers")]
public void ImageShouldResize(string name, IResampler sampler)
@@ -424,6 +452,7 @@
Directory.CreateDirectory("TestOutput/Skew");
}
+ // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
@@ -435,7 +464,7 @@
using (Image image = new Image(stream))
using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}"))
{
- image.Skew(45, 45, this.ProgressUpdate)
+ image.Skew(20, 10, this.ProgressUpdate)
.Save(output);
}
diff --git a/tests/TestWebsites/MVC/Properties/launchSettings.json b/tests/TestWebsites/MVC/Properties/launchSettings.json
new file mode 100644
index 000000000..0ce7c413b
--- /dev/null
+++ b/tests/TestWebsites/MVC/Properties/launchSettings.json
@@ -0,0 +1,19 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:55993/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file