[Drawing API] Add ModifyCTM (Matrix m) to Xwt.Drawing.Context API

This provides the link between the Matrix transforms facilities and Drawing.Contexts
An sample of using this for specialised transforms has been added to DrawingTransforms.cs
and unit tests hve been aded to Testing/Tests/TransformTests.cs
This commit is contained in:
hwthomas 2013-07-28 15:21:12 +01:00
Родитель ff4ad28bca
Коммит c98579a302
8 изменённых файлов: 179 добавлений и 16 удалений

Просмотреть файл

@ -50,7 +50,8 @@ namespace Samples
public virtual void Transforms (Xwt.Drawing.Context ctx, double x, double y)
{
Rotate (ctx, x, y);
Scale (ctx, x + 120, y);
Scale (ctx, x + 140, y);
Reflect (ctx, x+20, y + 100);
}
public virtual void Rotate (Xwt.Drawing.Context ctx, double x, double y)
@ -126,6 +127,51 @@ namespace Samples
ctx.Restore ();
}
/// <summary>
/// Illustrates the use of matrix transforms to skew and reflect text
/// </summary>
public void Reflect (Context ctx, double x, double y)
{
ctx.Save ();
ctx.SetLineWidth (1);
TextLayout layout = new TextLayout ();
layout.Text = "Reflected and Skewed Text";
layout.Font = Font.WithSize (16);
Size size = layout.GetSize ();
Rectangle r = new Rectangle (Point.Zero, size);
// Draw text with no transformations at (x+0.5, y+0.5)
ctx.Translate (x+0.5, y+0.5); // final move to specified location
ctx.SetColor (Colors.Blue);
ctx.DrawTextLayout (layout, 0, 0);
// Use Matrix transforms to reflect Y-values and skew X-values by -0.5*Y
// Note that transforms are prepended, so are actioned in reverse order
// This is the same order that Backend Context transforms are applied
Matrix m = new Matrix (); // Identity matrix
Matrix s = new Matrix (1.0, 0.0, // new skew matrix
-0.5, 1.0,
0.0, 0.0);
m.TranslatePrepend (0, size.Height); // Shift text back to place
m.Prepend (s); // Skew X-values
m.ScalePrepend (1, -1); // Reflect text Y-values
m.TranslatePrepend (0, -size.Height); // Shift text base to (0,0)
ctx.ModifyCTM (m); // NB ctx.Translate (x+0.5, y+0.5) still active
ctx.SetColor (Colors.DarkGray);
ctx.Rectangle (r);
ctx.Fill ();
ctx.SetColor (Colors.LightBlue);
ctx.DrawTextLayout (layout, 0, 0);
ctx.Restore ();
}
}
}

Просмотреть файл

@ -41,6 +41,9 @@ namespace Xwt
[TestFixture]
public class TransformTests
{
// There is only a single context used in all tests,
// so it MUST be Saved and Restored for each test.
const double DELTA = 0.000000001d;
ImageBuilder ib = null;
@ -49,8 +52,11 @@ namespace Xwt
[TestFixtureSetUp]
public void Init ()
{
ib = new ImageBuilder (10, 10);
ib = new ImageBuilder (1, 1);
context = ib.Context;
Matrix m1 = Matrix.Identity;
Matrix m2 = context.GetCTM ();
CheckMatrix (m1, m2);
}
[TestFixtureTearDown]
@ -58,12 +64,11 @@ namespace Xwt
{
if (context != null)
context.Dispose ();
if (ib != null)
ib.Dispose ();
}
private Context NewContext {
private Context Context {
get { return context; }
}
@ -77,19 +82,14 @@ namespace Xwt
Assert.AreEqual (expected.OffsetY, actual.OffsetY, DELTA);
}
[Test]
public void NewCTM ()
{
Matrix mI = Matrix.Identity;
Context ctx = NewContext;
CheckMatrix (mI, ctx.GetCTM ());
}
[Test]
public void Translate ()
{
Matrix m1, m2;
Context ctx = NewContext;
Context ctx = Context;
ctx.Save ();
// Check with a range of -ve and +ve offsets
for (double tx = -1; tx <= 1; tx += 0.25) {
@ -110,13 +110,15 @@ namespace Xwt
CheckMatrix (m1, m2);
ctx.Restore ();
}
ctx.Restore ();
}
[Test]
public void Scale ()
{
Matrix m1, m2;
Context ctx = NewContext;
Context ctx = Context;
ctx.Save ();
// Scaling with a zero scale-factor results in a non-invertible matrix
// This fails with Cairo, so avoid zero as one of the test values
@ -138,13 +140,15 @@ namespace Xwt
CheckMatrix (m1, m2);
ctx.Restore ();
}
ctx.Restore ();
}
[Test]
public void Rotate ()
{
Matrix m1, m2;
Context ctx = NewContext;
Context ctx = Context;
ctx.Save ();
for (double theta = 0; theta <= 360; theta += 30) {
ctx.Save ();
@ -155,6 +159,76 @@ namespace Xwt
CheckMatrix (m1, m2);
ctx.Restore ();
}
ctx.Restore ();
}
[Test]
public void RotateAndTranslate ()
{
// Transforms are Prepended to the CTM, so they are done in reverse order.
// At present, this must be stated explicitly in the Matrix calls, but may
// be worth changing so that Prepend is the default (to match the Backends).
Matrix m1, m2;
Context ctx = Context;
ctx.Save ();
for (double theta = 30; theta <= 360; theta += 30) {
ctx.Save ();
ctx.Translate (100, 0); // done last
ctx.Rotate (theta); // done first (about the origin)
m1 = Matrix.Identity;
m1.TranslatePrepend (100, 0);
m1.RotatePrepend (theta);
m2 = ctx.GetCTM ();
CheckMatrix (m1, m2);
ctx.Restore ();
}
ctx.Restore ();
}
[Test]
public void ModifyCTM ()
{
// Checks that Matrix and Context transforms match, and that applying
// a matrix transform to the Context CTM produces the expected result
double theta = 30;
double x = 50;
double y = 30;
Context ctx = Context;
ctx.Save ();
Matrix m1 = Matrix.Identity;
Matrix ctm;
// Apply and compare several transforms
ctx.Save ();
ctx.Scale (2, 3);
m1.ScalePrepend (2, 3);
ctm = ctx.GetCTM ();
CheckMatrix (m1, ctm);
ctx.Translate (x, y);
m1.TranslatePrepend (x, y);
ctm = ctx.GetCTM ();
CheckMatrix (m1, ctm);
ctx.Rotate (theta);
m1.RotatePrepend (theta);
ctm = ctx.GetCTM ();
CheckMatrix (m1, ctm);
// Check ModifyCTM matches combined Matrix Transform
ctx.Restore ();
ctx.ModifyCTM (m1);
ctm = ctx.GetCTM ();
CheckMatrix (m1, ctm);
ctx.Restore ();
}
}

Просмотреть файл

@ -346,6 +346,13 @@ namespace Xwt.CairoBackend
gc.Context.Translate (tx, ty);
}
public override void ModifyCTM (object backend, Matrix m)
{
CairoContextBackend gc = (CairoContextBackend)backend;
Cairo.Matrix t = new Cairo.Matrix (m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY);
gc.Context.Transform (t);
}
public override Matrix GetCTM (object backend)
{
Cairo.Matrix t = ((CairoContextBackend)backend).Context.Matrix;

Просмотреть файл

@ -319,6 +319,14 @@ namespace Xwt.Mac
((CGContextBackend)backend).Context.TranslateCTM ((float)tx, (float)ty);
}
public override void ModifyCTM (object backend, Matrix m)
{
CGAffineTransform t = new CGAffineTransform ((float)m.M11, (float)m.M12,
(float)m.M21, (float)m.M22,
(float)m.OffsetX, (float)m.OffsetY);
((CGContextBackend)backend).Context.ConcatCTM (t);
}
public override Matrix GetCTM (object backend)
{
CGAffineTransform t = GetContextTransform ((CGContextBackend)backend);

Просмотреть файл

@ -297,6 +297,13 @@ namespace Xwt.WPFBackend
c.PushTransform (t);
}
public override void ModifyCTM (object backend, Drawing.Matrix m)
{
var c = (DrawingContext)backend;
MatrixTransform t = new MatrixTransform (m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY);
c.PushTransform (t);
}
public override Drawing.Matrix GetCTM (object backend)
{
var c = (DrawingContext)backend;

Просмотреть файл

@ -70,6 +70,8 @@ namespace Xwt.Backends
public abstract void Translate (object backend, double tx, double ty);
public abstract void ModifyCTM (object backend, Matrix transform);
public abstract Matrix GetCTM (object backend);
public abstract bool IsPointInStroke (object backend, double x, double y);

Просмотреть файл

@ -222,6 +222,18 @@ namespace Xwt.Drawing
handler.Translate (Backend, p.X, p.Y);
}
/// <summary>
/// Modifies the Current Transformation Matrix (CTM) by prepending the Matrix transform specified
/// </summary>
/// <remarks>
/// This enables any 'non-standard' transforms (eg skew, reflection) to be used for drawing,
/// and provides the link to the extra transform capabilities provided by Xwt.Drawing.Matrix
/// </remarks>
public void ModifyCTM (Matrix transform)
{
handler.ModifyCTM (Backend, transform);
}
/// <summary>
/// Returns a copy of the current transformation matrix (CTM)
/// </summary>

Просмотреть файл

@ -548,6 +548,13 @@ namespace Xwt.Drawing
ctx.NativeContextHandler.Translate (ctx.NativeBackend, tx, ty);
}
public override void ModifyCTM (object backend, Matrix t)
{
var ctx = (VectorBackend)backend;
CreateNativePathBackend (ctx);
ctx.NativeContextHandler.ModifyCTM (ctx.NativeBackend, t);
}
public override Matrix GetCTM (object backend)
{
var ctx = (VectorBackend)backend;