Re: Shouldn't Cairo use/offer degrees rather than radians?

David Kastrup <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
Bill Spitzak <[email protected]> writes:

> On Mon, Jul 10, 2017 at 10:10 PM, David Kastrup <[email protected]> wrote:
>> Gregor Mückl <[email protected]> writes:
>>
>>> Don't pin this only on the compiler, at least on x86. The old x87
>>> compatible FPU instructions (obsolete in 64 bit mode) use internal
>>> registers with significant longer mantissa than a double. The length
>>> of the mantissa was not even fixed between implementations. However,
>>> storing values from the FPU to memory results in truncation in this
>>> case. This was "fixed" in SSE and newer intruction sets that are now
>>> fixed at 32 bits for floats and 64 bits for doubles for internal
>>> registers.
>>>
>>> I believe that this thread is starting to demonstrate that hunting for
>>> numeric precision beyond a certain point using floating point
>>> arithmetic is a fool's errand.
>>
>> The proposed code for setting angles in degrees is numerically precise
>> at right angles for any floating point representation.
>>
>> Stuff like M_PI/2 or even M_PI will always result in hunts for numeric
>> precision since pi does not have an exact floating point representation.
>>
>> This thread was about a proposed user interface that makes hunting for
>> numeric precision a non-issue for the most prominent cases while
>> representing angles in a common human-accessible format that other
>> graphic systems and formats use as well.
>>
>> So the conclusion "let's not bother offering something better because we
>> cannot conclusively figure out how bad the current situation is on
>> different systems respectively" is not really what I was pitching for.
>
> The problem is that it is not better, and providing the api will
> mislead people into thinking it is better.

This API can turn exactly by multiples of 90 degrees.  The existing API
cannot.

> You wanted two rotates by 45 degrees to be perfect.

Uh, no?  Two rotates by 45 degrees illustrate a _compromise_.  The
degree of rotation will generally be a perfect 90 degrees because of all
rotation matrix elements having the same magnitude, the total magnitude
(determinant of the scaling matrix I think) will lightly be slightly
more wrong than the magnitude of the radian API (which likely also fails
to be 1 due to sin/cos of M_PI/4 in floating point also being
approximations).

> However that is not how Cairo is implemented and changing it would
> require considerable effort for no real win. A rotate of 45 degrees is
> turned into a matrix containing sqrt(2)/2 which is not stored
> accurately.

Did you even read the patch and its rationale?  Or are you making up
that straw man on the fly?  Multiples of 90 degrees are perfect.  There
are currently several fast paths in Cairo's code paths which actually
_check_ for that kind of perfection.

> The exact same value is stored whether the rotation is sent as degrees
> or radians.

DID YOU LOOK AT THE PATCH?????  I cannot believe you did when you state
this.

> Any implementation that blindly does sin(angle*(M_PI/180)) will
> produce the exact same error for right angles whether they are given
> in degrees or radians.

The patch I sent did not do this.

> So simply making the api be degrees with the easiest implementation
> will not fix anything.

DID YOU LOOK AT THE PATCH?????

To make this easier for you, I append it _again_ as an attachment to
this mail.

> I would like to make a more limited request of Cairo:
>
> 1. Change cairo_matrix_init_rotate to detect multiples of M_PI_2 and
> produce integers.

This does not work reliably because pi and consequently pi/2 do not have
exact floating point representations.  As a result, M_PI+0.5*M_PI may or
may not be equal to 1.5*M_PI.  This has to interpret values as something
that they aren't and consequently introduces errors.  You'd need to work
with tolerances, and every window of tolerance is equivalent to a window
of erroneous interpretation.  You'd need different windows for every
different floating point format.

In contrast, 0, 45, 90, ... have exact floating point representations in
any commonly used floating point format.

> From what I can see subtracting M_PI_2*n where n is
> rint(angle/M_PI_2), and then doing sin/cos works pretty good. This
> will prevent surprises in the resulting matrix. I tested some stuff in
> gnuplot and any discontinuity in the plots is below 2e-16.
>
> 2. Add an api to rotate so the x axis passes through an x,y point (and
> possibly scales by hypot(x,y)). This would provide an "accurate"
> rotate for a whole lot of cases that actually come up in real
> graphics.

For multiples of 90 degrees, you can "trivially" just specify the
transform matrix, yet nobody does.  This is not how people think, and we
are talking about an API for people.

David Kastrup

-- 
cairo mailing list
[email protected]
https://lists.cairographics.org/mailman/listinfo/cairo
0001-Implement-cairo_matrix_init_rotate_deg-and-cairo_mat.patch (text/x-diff, 5.8 KB)
From a057b17b5018000435cc011d7a2d20072457badb Mon Sep 17 00:00:00 2001
From: David Kastrup <[email protected]>
Date: Fri, 30 Jun 2017 07:01:26 +0200
Subject: [PATCH] Implement cairo_matrix_init_rotate_deg and
 cairo_matrix_rotate_deg

Those offer variants of cairo_matrix_init_rotate and cairo_matrix_rotate
that are numerically perfect at right angles without introducing
discontinuities.
---
 doc/public/cairo-sections.txt |  2 +
 src/cairo-matrix.c            | 92 +++++++++++++++++++++++++++++++++++++++++++
 src/cairo.h                   |  7 ++++
 src/cairoint.h                |  1 +
 4 files changed, 102 insertions(+)

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index 7b04ae7b3..ac5e566a0 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -430,9 +430,11 @@ cairo_matrix_init_identity
 cairo_matrix_init_translate
 cairo_matrix_init_scale
 cairo_matrix_init_rotate
+cairo_matrix_init_rotate_deg
 cairo_matrix_translate
 cairo_matrix_scale
 cairo_matrix_rotate
+cairo_matrix_rotate_deg
 cairo_matrix_invert
 cairo_matrix_multiply
 cairo_matrix_transform_distance
diff --git a/src/cairo-matrix.c b/src/cairo-matrix.c
index ae498f515..8728bcdee 100644
--- a/src/cairo-matrix.c
+++ b/src/cairo-matrix.c
@@ -312,6 +312,98 @@ cairo_matrix_rotate (cairo_matrix_t *matrix, double radians)
 }
 
 /**
+ * cairo_matrix_init_rotate_deg:
+ * @matrix: a #cairo_matrix_t
+ * @degrees: angle of rotation, in degrees. The direction of rotation
+ * is defined such that positive angles rotate in the direction from
+ * the positive X axis toward the positive Y axis. With the default
+ * axis orientation of cairo, positive angles rotate in a clockwise
+ * direction.
+ * For angles that are multiples of 90 degrees, the transformation
+ * matrix is numerically exact.
+ *
+ * Initializes @matrix to a
+ * transformation that rotates by @degrees.
+ *
+ * Since: 1.16
+ **/
+void
+cairo_matrix_init_rotate_deg (cairo_matrix_t *matrix,
+			      double degrees)
+{
+    double  s;
+    double  c;
+
+    if (unlikely (degrees <= -540.0 || degrees >= 540.0))
+      degrees = fmod (degrees, 360.0);
+    /* Now |degrees| < 540.0, and the absolute size is not larger than
+       before, so we haven't lost precision. */
+    if (unlikely (degrees <= -180.0))
+      degrees += 360.0;
+    else if (unlikely (degrees > 180.0))
+      degrees -= 360.0;
+    /* Now -180.0 < degrees <= 180.0 and we still haven't lost
+       precision.  We don't work with angles greater than 90 degrees
+       absolute in order to minimize how rounding errors of M_PI/180
+       affect the result.  The "handover" between one sine expression
+       to the next happens at angles of +-90 degrees where
+       sin(pi/2+eps) is about (1-eps^2/2).  Since the difference to
+       pi/2 should be quite small, the sine will be numerically 1 here.
+
+       Sign of the sine is chosen to avoid -0.0 in results.  This
+       version delivers exactly equal magnitude on x/y for odd
+       multiples of 45 degrees. */
+
+    if (degrees >= 0) {
+      c = sin ((90 - degrees) * (M_PI/180.0));
+      if (degrees > 90)
+	s = sin ((180 - degrees) * (M_PI/180.0));
+      else
+	s = sin (degrees * (M_PI/180.0));
+    } else {
+      c = sin ((90 + degrees) * (M_PI/180.0));
+      if (degrees < -90)
+	s = sin ((-180 - degrees) * (M_PI/180.0));
+      else
+	s = sin (degrees * (M_PI/180.0));
+    }
+
+    cairo_matrix_init (matrix,
+		       c, s,
+		       -s, c,
+		       0, 0);
+}
+slim_hidden_def(cairo_matrix_init_rotate_deg);
+
+/**
+ * cairo_matrix_rotate_deg:
+ * @matrix: a #cairo_matrix_t
+ * @degrees: angle of rotation, in degrees. The direction of rotation
+ * is defined such that positive angles rotate in the direction from
+ * the positive X axis toward the positive Y axis. With the default
+ * axis orientation of cairo, positive angles rotate in a clockwise
+ * direction.
+ * For angles that are multiples of 90 degrees, the change to the
+ * transformation matrix is numerically exact.
+ *
+ * Applies rotation by @degrees to the transformation in
+ * @matrix. The effect of the new transformation is to first rotate the
+ * coordinates by @degrees, then apply the original transformation
+ * to the coordinates.
+ *
+ * Since: 1.16
+ **/
+void
+cairo_matrix_rotate_deg (cairo_matrix_t *matrix, double degrees)
+{
+    cairo_matrix_t tmp;
+
+    cairo_matrix_init_rotate_deg (&tmp, degrees);
+
+    cairo_matrix_multiply (matrix, &tmp, matrix);
+}
+
+/**
  * cairo_matrix_multiply:
  * @result: a #cairo_matrix_t in which to store the result
  * @a: a #cairo_matrix_t
diff --git a/src/cairo.h b/src/cairo.h
index 32fc88b17..310f4c6d5 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -3026,6 +3026,10 @@ cairo_matrix_init_rotate (cairo_matrix_t *matrix,
 			  double radians);
 
 cairo_public void
+cairo_matrix_init_rotate_deg (cairo_matrix_t *matrix,
+			      double degrees);
+
+cairo_public void
 cairo_matrix_translate (cairo_matrix_t *matrix, double tx, double ty);
 
 cairo_public void
@@ -3034,6 +3038,9 @@ cairo_matrix_scale (cairo_matrix_t *matrix, double sx, double sy);
 cairo_public void
 cairo_matrix_rotate (cairo_matrix_t *matrix, double radians);
 
+cairo_public void
+cairo_matrix_rotate_deg (cairo_matrix_t *matrix, double degrees);
+
 cairo_public cairo_status_t
 cairo_matrix_invert (cairo_matrix_t *matrix);
 
diff --git a/src/cairoint.h b/src/cairoint.h
index 4fedf861d..f04e81131 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1941,6 +1941,7 @@ slim_hidden_proto (cairo_mask);
 slim_hidden_proto (cairo_matrix_init);
 slim_hidden_proto (cairo_matrix_init_identity);
 slim_hidden_proto (cairo_matrix_init_rotate);
+slim_hidden_proto (cairo_matrix_init_rotate_deg);
 slim_hidden_proto (cairo_matrix_init_scale);
 slim_hidden_proto (cairo_matrix_init_translate);
 slim_hidden_proto (cairo_matrix_invert);
-- 
2.11.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.