Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the Transform API #775

Merged
merged 34 commits into from
Nov 28, 2018
Merged

Improve the Transform API #775

merged 34 commits into from
Nov 28, 2018

Conversation

JimBobSquarePants
Copy link
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This PR dramatically simplifies all affine and projective transforms and fixes some issues we had with affine transforms.

Two new classes have been introduced into the public domain which allow the composition of matrices:

  • AffineTransformBuilder
  • ProjectiveTransformBuilder

All Transform<TPixel> methods now utilize these two classes which allows us to automatically resize canvases to cater for the transformed vectors.

For example an affine transform can be composed in the following manner:

var builder = new AffineTransformBuilder(image.Size())
    .AppendRotateMatrixDegrees(angleDeg)
    .AppendScaleMatrix(new SizeF(sx, sy))
    .AppendTranslationMatrix(new PointF(tx, ty));

image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic));

Both classes also have a Rectangle constructor overload that allows the transforming of areas of interest in the source image.

var rectangle = new Rectangle(48, 0, 48, 24);

var builder = new AffineTransformBuilder(rectangle)
    .AppendScaleMatrix(new SizeF(2, 1.5F));

image.Mutate(i => i.Transform(builder, KnownResamplers.Spline));

Additionally several layers of abstraction have now been removed. And performance of transforms improved by around 10%.

@codecov
Copy link

codecov bot commented Nov 11, 2018

Codecov Report

Merging #775 into master will increase coverage by 0.04%.
The diff coverage is 94.6%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #775      +/-   ##
==========================================
+ Coverage   88.57%   88.61%   +0.04%     
==========================================
  Files        1005     1008       +3     
  Lines       42859    42959     +100     
  Branches     3162     3162              
==========================================
+ Hits        37961    38067     +106     
+ Misses       4203     4191      -12     
- Partials      695      701       +6
Impacted Files Coverage Δ
...ests/Processing/Transforms/AffineTransformTests.cs 98.42% <100%> (-0.08%) ⬇️
...rocessing/Processors/Transforms/RotateProcessor.cs 100% <100%> (ø) ⬆️
.../Processing/Processors/Transforms/SkewProcessor.cs 84.61% <100%> (+9.61%) ⬆️
.../Processors/Transforms/AffineTransformProcessor.cs 100% <100%> (ø) ⬆️
...sts/Processing/Transforms/TransformsHelpersTest.cs 100% <100%> (ø) ⬆️
src/ImageSharp/Processing/TransformExtensions.cs 100% <100%> (+45.45%) ⬆️
src/ImageSharp/Common/Helpers/ImageMaths.cs 87.17% <100%> (ø) ⬆️
.../Processing/Transforms/ProjectiveTransformTests.cs 97.29% <100%> (+0.07%) ⬆️
.../Processing/Transforms/TransformBuilderTestBase.cs 100% <100%> (ø)
...s/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs 57.14% <100%> (+0.69%) ⬆️
... and 27 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c7eb18f...e8f73b4. Read the comment docs.

@JimBobSquarePants JimBobSquarePants changed the title Js/better transforms Improve the Transform API Nov 22, 2018
@JimBobSquarePants
Copy link
Member Author

Bump 😉

@antonfirsov
Copy link
Member

@JimBobSquarePants I will review this during the weekend.

Copy link
Member

@antonfirsov antonfirsov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job! Can't spot anything fundamentally wrong here, but I think we need to address a few points before merging this:

  • API suggestions (simplification, completeness)
  • Something's wrong on .NET 4.6.2 (on my I7-7700HQ machine)
  • Moar and better tests for transform builders

src/ImageSharp/Processing/AffineTransformBuilder.cs Outdated Show resolved Hide resolved
src/ImageSharp/Processing/AffineTransformBuilder.cs Outdated Show resolved Hide resolved
src/ImageSharp/Processing/AffineTransformBuilder.cs Outdated Show resolved Hide resolved
/// <summary>
/// A helper class for constructing <see cref="Matrix4x4"/> instances for use in projective transforms.
/// </summary>
public class ProjectiveTransformBuilder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my review comments for AffineTransfrormBuilder are also valid for ProjectiveTransformBuilder.

this.matrices.Add(matrix);
return this;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to also add the Rotate/Scale/Translate/Skew methods already present in AffineTransformBuilder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are they possible with Matrix4x4?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Affine is a subset of Projective. Not sure about the details with the Matrix4x4 class, but I guess you need to use the 3D API-s with Z=0 for translation and Z=1 for scale-like stuff.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically yeah I guess. Feel free to have a pop.

@@ -17,7 +17,8 @@ public class AffineTransformTests
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether it's related to the changes of the PR, but on my PC tests are failing for non-core2.1 targets. Something is wrong with the alpha blending:
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh oh!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this error is specific to transforms. This image never runs through that code but is a span copy. I think it's got something to do with Rgba32.

rotate_withrotatetypeenum_testpattern100x50_none


Assert.Equal(b1.BuildMatrix(), b2.BuildMatrix());
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need better coverage for both AffineTransformBuilder and ProjectiveTransformBuilder! Common features could be tested with the same test code. I'll see whether I can help here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both have 100% coverage as far as I’m aware.

Copy link
Member

@antonfirsov antonfirsov Nov 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specific creation methods (rotate etc.) seem uncovered for me + we can have a bit more semantical testing.

An idea that's easy to implement: a point transformed by a matrix created with the builder using a series of basic transforms should get to the expected position.

src/ImageSharp/Processing/AffineTransformBuilder.cs Outdated Show resolved Hide resolved
src/ImageSharp/Processing/AffineTransformBuilder.cs Outdated Show resolved Hide resolved
this.TransformMatrix = matrix;
this.TargetDimensions = targetDimensions;
this.TargetDimensions = TransformUtils.GetTransformedSize(sourceSize, matrix);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether the logic implemented in GetTransformedSize(...) fits the needs of all users. Maybe we should expose a AffineTransformProcessor(matrix, sampler, targetSize) constructor instead, and deal with the rest in the extension methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s seems to cover everything I could throw at it, shrinking and growing when need be. There’s actually similar code for handling free transforms in the net framework.

@antonfirsov
Copy link
Member

antonfirsov commented Nov 25, 2018

@JimBobSquarePants one other API suggestion (which is pretty much related to my previous suggestions):

Asking the user to feed the source image size to Transform Builders seems unnecessary to me. The size is available on IImageProcessingContext<T> when a transform is being applied. We can prepend it to the matrix created by the Transform Builders. This will simplify the API usage on consumer side, and allow more flexibility.

@antonfirsov
Copy link
Member

antonfirsov commented Nov 25, 2018

@JimBobSquarePants OK, how about this:

I push some more tests + extend the projective API right now, you deal with the rest?

How about the method namings? Should I change them? Still feel the word Matrix is unnecessary in method names, and it will become somewhat outdated if we shift to the matrix factory design.

@JimBobSquarePants
Copy link
Member Author

Thanks for the review. Yeah sounds good. Let’s go ahead with naming changes, it is awfully wordy just now.

@antonfirsov
Copy link
Member

antonfirsov commented Nov 26, 2018

@JimBobSquarePants I also implemented my size-decoupling idea within the Affine API-s, but this is all for today. Projective API-s still need to be adapted.

And we still may want to find a workaround for the .NET 4.6.2 issue, however it's debug-only. (I think it is Vector2 tricking us again.)

@JimBobSquarePants
Copy link
Member Author

And we still may want to find a workaround for the .NET 4.6.2 issue, however it's debug-only. (I think it is Vector2 tricking us again.)

@antonfirsov When you have time could you check this again? When I updated Core etc I think I got an implicit upgrade to System.Numerics.Vectors 4.5.0 so I want to see if there's any changes. Otherwise we may have to do something like the following which is super crappy.

Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z;

@JimBobSquarePants
Copy link
Member Author

@antonfirsov Aside from the alpha issue in debug I think we're done here.

@antonfirsov
Copy link
Member

antonfirsov commented Nov 27, 2018

@JimBobSquarePants checking it now. Is the issue reproducible on your PC? (.NET 4.6.2/4.7.2 test execution + Debug)

@antonfirsov
Copy link
Member

antonfirsov commented Nov 27, 2018

@JimBobSquarePants for me it still fails.

We either should do the Vector2 -> Vector4 workaround, or leave a note somewhere in the contribution guide that Debug test execution is only supported on .NET Core 2.1.

@antonfirsov
Copy link
Member

antonfirsov commented Nov 27, 2018

@JimBobSquarePants what is still missing:

  • Unit Tests for TransformUtils: Should test the created transformations for correctness using sample input points paired with expected transformed destination points. I would say this is critical, but maybe I can live without it, if we can't do it now.
  • Skew in ProjectiveTransformBuilder (Could be added in a follow up PR)
  • Unit tests for AppendSkew: Similar to rotation tests: we should test that the transformation being appended/prepended is the same transformation that is produced by TransformUtils. (Less critical, could be added in a follow up PR)

@JimBobSquarePants
Copy link
Member Author

JimBobSquarePants commented Nov 27, 2018

@antonfirsov I get 283 failed tests running in debug on net472. Even things as simple as Rgba32 equality. Considering we're using ushort.Equals there I don't think they are fixable.

Just tried to create a reduced sample for the Rgba32 comparison error. Seems to work fine, very frustrating.

@JimBobSquarePants
Copy link
Member Author

Unit Tests for TransformUtils: Should test the created transformations for correctness using sample input points paired with expected transformed destination points. I would say this is critical, but maybe I can live without it, if we can't do it now.

You're a hard taskmaster you know that right? 😝

@antonfirsov
Copy link
Member

Opend #780 to "deal" with the Debug mode issues, we probalby shouldn't bother working around this in product code any longer.

I don't want to be too hard at this PR, up to you to decide. I'm just trying to save our future selves from writing these tests when we touch the code again 😄

@JimBobSquarePants
Copy link
Member Author

@antonfirsov I've added all the Skew overloads to both builders and relevant additional tests for them also. Any additional tests we can add when cleaning up.

@JimBobSquarePants JimBobSquarePants merged commit f9c94e2 into master Nov 28, 2018
@JimBobSquarePants JimBobSquarePants deleted the js/better-transforms branch November 28, 2018 18:29
antonfirsov pushed a commit to antonfirsov/ImageSharp that referenced this pull request Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants