GOOD uses the BOX filter, and uses BILINEAR for all scales > 1/1.35.

BEST uses the LANCZOS2 filter. The size is chosen to produce normal
filtering up to a scale of 2, and square pixels with only slight
blurry borders beyond that. I think it looks really nice.

Uses NEAREST instead of BILINEAR, GOOD, or BEST if possible. Including
reflections, which the previous code did not check for.

The scale demo has added a pulldown so you can test good/best, and
also compare separable filters with nearest and bilinear.
---
 demos/scale.c         |   14 ++-
 demos/scale.ui        |   40 +++++--
 pixman/pixman-image.c |  275 +++++++++++++++++++++++++++++++++++--------------
 3 files changed, 239 insertions(+), 90 deletions(-)

diff --git a/demos/scale.c b/demos/scale.c
index 0b6c715..c3ca753 100644
--- a/demos/scale.c
+++ b/demos/scale.c
@@ -68,6 +68,15 @@ typedef struct
     int                value;
 } named_int_t;
 
+static const named_int_t filter_types[] =
+{
+    { "Separable",             PIXMAN_FILTER_SEPARABLE_CONVOLUTION },
+    { "Nearest",               PIXMAN_FILTER_NEAREST },
+    { "Bilinear",              PIXMAN_FILTER_BILINEAR },
+    { "Good",                  PIXMAN_FILTER_GOOD },
+    { "Best",                  PIXMAN_FILTER_BEST },
+};
+
 static const named_int_t filters[] =
 {
     { "Box",                   PIXMAN_KERNEL_BOX },
@@ -201,7 +210,9 @@ rescale (GtkWidget *may_be_null, app_t *app)
        gtk_adjustment_get_value (app->subsample_adjustment),
        gtk_adjustment_get_value (app->subsample_adjustment));
 
-    pixman_image_set_filter (app->original, 
PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, n_params);
+    pixman_image_set_filter (app->original,
+       get_value (app, filter_types, "filter_combo_box"),
+       params, n_params);
 
     pixman_image_set_repeat (
         app->original, get_value (app, repeats, "repeat_combo_box"));
@@ -343,6 +354,7 @@ app_new (pixman_image_t *original)
     widget = get_widget (app, "drawing_area");
     g_signal_connect (widget, "expose_event", G_CALLBACK (on_expose), app);
 
+    set_up_combo_box (app, "filter_combo_box", G_N_ELEMENTS (filter_types), 
filter_types);
     set_up_filter_box (app, "reconstruct_x_combo_box");
     set_up_filter_box (app, "reconstruct_y_combo_box");
     set_up_filter_box (app, "sample_x_combo_box");
diff --git a/demos/scale.ui b/demos/scale.ui
index ee985dd..b62cbfb 100644
--- a/demos/scale.ui
+++ b/demos/scale.ui
@@ -191,12 +191,23 @@
                     <property name="column_spacing">8</property>
                     <property name="row_spacing">6</property>
                     <child>
+                      <object class="GtkLabel" id="labelF">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" 
translatable="yes">&lt;b&gt;Filter:&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                    </child>
+                    <child>
                       <object class="GtkLabel" id="label4">
                         <property name="visible">True</property>
                         <property name="xalign">1</property>
                         <property name="label" 
translatable="yes">&lt;b&gt;Reconstruct X:&lt;/b&gt;</property>
                         <property name="use_markup">True</property>
                       </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                      </packing>
                     </child>
                     <child>
                       <object class="GtkLabel" id="label5">
@@ -206,7 +217,7 @@
                         <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="top_attach">1</property>
+                        <property name="top_attach">2</property>
                       </packing>
                     </child>
                     <child>
@@ -217,7 +228,7 @@
                         <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="top_attach">2</property>
+                        <property name="top_attach">3</property>
                       </packing>
                     </child>
                     <child>
@@ -228,7 +239,7 @@
                         <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="top_attach">3</property>
+                        <property name="top_attach">4</property>
                       </packing>
                     </child>
                     <child>
@@ -239,7 +250,7 @@
                         <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="top_attach">4</property>
+                        <property name="top_attach">5</property>
                       </packing>
                     </child>
                     <child>
@@ -250,7 +261,15 @@
                         <property name="use_markup">True</property>
                       </object>
                       <packing>
-                        <property name="top_attach">5</property>
+                        <property name="top_attach">6</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="filter_combo_box">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
                       </packing>
                     </child>
                     <child>
@@ -259,6 +278,7 @@
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
                       </packing>
                     </child>
                     <child>
@@ -267,7 +287,7 @@
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
-                        <property name="top_attach">1</property>
+                        <property name="top_attach">2</property>
                       </packing>
                     </child>
                     <child>
@@ -276,7 +296,7 @@
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
-                        <property name="top_attach">2</property>
+                        <property name="top_attach">3</property>
                       </packing>
                     </child>
                     <child>
@@ -285,7 +305,7 @@
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
-                        <property name="top_attach">3</property>
+                        <property name="top_attach">4</property>
                       </packing>
                     </child>
                     <child>
@@ -294,7 +314,7 @@
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
-                        <property name="top_attach">4</property>
+                        <property name="top_attach">5</property>
                       </packing>
                     </child>
                     <child>
@@ -304,7 +324,7 @@
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
-                        <property name="top_attach">5</property>
+                        <property name="top_attach">6</property>
                       </packing>
                     </child>
                   </object>
diff --git a/pixman/pixman-image.c b/pixman/pixman-image.c
index 1ff1a49..d7bfcf3 100644
--- a/pixman/pixman-image.c
+++ b/pixman/pixman-image.c
@@ -28,6 +28,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
+#include <math.h>
 
 #include "pixman-private.h"
 
@@ -274,112 +275,228 @@ compute_image_info (pixman_image_t *image)
                  FAST_PATH_X_UNIT_POSITIVE     |
                  FAST_PATH_Y_UNIT_ZERO         |
                  FAST_PATH_AFFINE_TRANSFORM);
+       switch (image->common.filter)
+       {
+       case PIXMAN_FILTER_CONVOLUTION:
+           break;
+       case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
+           flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+           break;
+       default:
+           flags |= (FAST_PATH_NEAREST_FILTER | 
FAST_PATH_NO_CONVOLUTION_FILTER);
+           break;
+       }
     }
     else
     {
+       pixman_fixed_t (*m)[3] = image->common.transform->matrix;
+       double dx, dy;
+       int xsubsample, ysubsample;
+       int nearest_ok;
+
        flags |= FAST_PATH_HAS_TRANSFORM;
 
-       if (image->common.transform->matrix[2][0] == 0                  &&
-           image->common.transform->matrix[2][1] == 0                  &&
-           image->common.transform->matrix[2][2] == pixman_fixed_1)
+       nearest_ok = FALSE;
+
+       if (m[2][0] == 0                        &&
+           m[2][1] == 0                        &&
+           m[2][2] == pixman_fixed_1)
        {
            flags |= FAST_PATH_AFFINE_TRANSFORM;
 
-           if (image->common.transform->matrix[0][1] == 0 &&
-               image->common.transform->matrix[1][0] == 0)
+           if (m[0][1] == 0 && m[1][0] == 0)
            {
-               if (image->common.transform->matrix[0][0] == -pixman_fixed_1 &&
-                   image->common.transform->matrix[1][1] == -pixman_fixed_1)
+               flags |= FAST_PATH_SCALE_TRANSFORM;
+               if (abs(m[0][0]) == pixman_fixed_1 &&
+                   abs(m[1][1]) == pixman_fixed_1)
                {
-                   flags |= FAST_PATH_ROTATE_180_TRANSFORM;
+                   nearest_ok = TRUE;
+                   if (m[0][0] < 0 && m[1][1] < 0)
+                       flags |= FAST_PATH_ROTATE_180_TRANSFORM;
                }
-               flags |= FAST_PATH_SCALE_TRANSFORM;
            }
-           else if (image->common.transform->matrix[0][0] == 0 &&
-                    image->common.transform->matrix[1][1] == 0)
+           else if (m[0][0] == 0 && m[1][1] == 0)
            {
-               pixman_fixed_t m01 = image->common.transform->matrix[0][1];
-               pixman_fixed_t m10 = image->common.transform->matrix[1][0];
-
-               if (m01 == -pixman_fixed_1 && m10 == pixman_fixed_1)
-                   flags |= FAST_PATH_ROTATE_90_TRANSFORM;
-               else if (m01 == pixman_fixed_1 && m10 == -pixman_fixed_1)
-                   flags |= FAST_PATH_ROTATE_270_TRANSFORM;
+               if (abs(m[0][1]) == pixman_fixed_1 &&
+                   abs(m[1][0]) == pixman_fixed_1)
+               {
+                   nearest_ok = TRUE;
+                   if (m[0][1] < 0 && m[1][0] > 0)
+                       flags |= FAST_PATH_ROTATE_90_TRANSFORM;
+                   else if (m[0][1] > 0 && m[1][0] < 0)
+                       flags |= FAST_PATH_ROTATE_270_TRANSFORM;
+               }
            }
        }
 
-       if (image->common.transform->matrix[0][0] > 0)
+       if (nearest_ok)
+       {
+           /* reject non-integer translation: */
+           if (pixman_fixed_frac (m[0][2] | m[1][2]))
+               nearest_ok = FALSE;
+           /* FIXME: there are some affine-test failures, showing
+            * that handling of BILINEAR and NEAREST filter is not
+            * quite equivalent when getting close to 32K for the
+            * translation components of the matrix. That's likely
+            * some bug, but for now just skip BILINEAR->NEAREST
+            * optimization in this case.
+            */
+           else if (abs(m[0][2] | m[1][2]) > pixman_int_to_fixed (30000))
+               nearest_ok = FALSE;
+       }
+
+       if (m[0][0] > 0)
            flags |= FAST_PATH_X_UNIT_POSITIVE;
 
-       if (image->common.transform->matrix[1][0] == 0)
+       if (m[1][0] == 0)
            flags |= FAST_PATH_Y_UNIT_ZERO;
-    }
 
-    /* Filter */
-    switch (image->common.filter)
-    {
-    case PIXMAN_FILTER_NEAREST:
-    case PIXMAN_FILTER_FAST:
-       flags |= (FAST_PATH_NEAREST_FILTER | FAST_PATH_NO_CONVOLUTION_FILTER);
-       break;
+       switch (image->common.filter)
+       {
+       case PIXMAN_FILTER_NEAREST:
+       case PIXMAN_FILTER_FAST:
+           flags |= (FAST_PATH_NEAREST_FILTER | 
FAST_PATH_NO_CONVOLUTION_FILTER);
+           break;
 
-    case PIXMAN_FILTER_BILINEAR:
-    case PIXMAN_FILTER_GOOD:
-    case PIXMAN_FILTER_BEST:
-       flags |= (FAST_PATH_BILINEAR_FILTER | FAST_PATH_NO_CONVOLUTION_FILTER);
+       case PIXMAN_FILTER_BILINEAR:
+           if (nearest_ok)
+               flags |= (FAST_PATH_NEAREST_FILTER | 
FAST_PATH_NO_CONVOLUTION_FILTER);
+           else
+               flags |= (FAST_PATH_BILINEAR_FILTER | 
FAST_PATH_NO_CONVOLUTION_FILTER);
+           break;
 
-       /* Here we have a chance to optimize BILINEAR filter to NEAREST if
-        * they are equivalent for the currently used transformation matrix.
-        */
-       if (flags & FAST_PATH_ID_TRANSFORM)
-       {
-           flags |= FAST_PATH_NEAREST_FILTER;
-       }
-       else if (
-           /* affine and integer translation components in matrix ... */
-           ((flags & FAST_PATH_AFFINE_TRANSFORM) &&
-            !pixman_fixed_frac (image->common.transform->matrix[0][2] |
-                                image->common.transform->matrix[1][2])) &&
-           (
-               /* ... combined with a simple rotation */
-               (flags & (FAST_PATH_ROTATE_90_TRANSFORM |
-                         FAST_PATH_ROTATE_180_TRANSFORM |
-                         FAST_PATH_ROTATE_270_TRANSFORM)) ||
-               /* ... or combined with a simple non-rotated translation */
-               (image->common.transform->matrix[0][0] == pixman_fixed_1 &&
-                image->common.transform->matrix[1][1] == pixman_fixed_1 &&
-                image->common.transform->matrix[0][1] == 0 &&
-                image->common.transform->matrix[1][0] == 0)
-               )
-           )
-       {
-           /* FIXME: there are some affine-test failures, showing that
-            * handling of BILINEAR and NEAREST filter is not quite
-            * equivalent when getting close to 32K for the translation
-            * components of the matrix. That's likely some bug, but for
-            * now just skip BILINEAR->NEAREST optimization in this case.
+       case PIXMAN_FILTER_GOOD:
+           if (nearest_ok) {
+               flags |= (FAST_PATH_NEAREST_FILTER | 
FAST_PATH_NO_CONVOLUTION_FILTER);
+               break;
+           }
+
+           /* Compute filter sizes. This is the bounding box of a
+            * diameter=1 circle transformed by the matrix. Scaling
+            * down produces values greater than 1.
+            *
+            * For non-affine the circle is centered on one of the 4
+            * points 1,1 away from the origin. Which one depends on
+            * the signs of the values in the last row of the matrix,
+            * chosen to avoid dividing by zero.
+            *
+            * This division factor both accounts for the w component
+            * and converts from fixed to float.
             */
-           pixman_fixed_t magic_limit = pixman_int_to_fixed (30000);
-           if (image->common.transform->matrix[0][2] <= magic_limit  &&
-               image->common.transform->matrix[1][2] <= magic_limit  &&
-               image->common.transform->matrix[0][2] >= -magic_limit &&
-               image->common.transform->matrix[1][2] >= -magic_limit)
-           {
-               flags |= FAST_PATH_NEAREST_FILTER;
+           dy = 1.0 / (abs(m[2][0]) + abs(m[2][1]) + abs(m[2][2]));
+           /* There are some signs that hypot is faster with numbers near 1
+            * so the division is done first. Mathematically it should work
+            * to divide afterwards.
+            */
+           dx = hypot (m[0][0] * dy, m[0][1] * dy);
+           dy = hypot (m[1][0] * dy, m[1][1] * dy);
+
+           /* At scales above 1/1.35 this uses the bilinear filter.
+            * This is identical to box at size 1, and I judged the
+            * artifacts for the smaller scales to not be worse than
+            * the box filter.
+            *
+            * Filters size is clamped to 16 to prevent extreme slowness.
+            */
+           if (dx <= 1.35) {
+               if (dy <= 1.35) {
+                   flags |= (FAST_PATH_BILINEAR_FILTER |
+                             FAST_PATH_NO_CONVOLUTION_FILTER);
+                   break;
+               }
+               dx = 1.0;
+           } else if (dx > 16.0)
+               dx = 16.0;
+           if (dy <= 1.35)
+               dy = 1.0;
+           else if (dy > 16.0)
+               dy = 16.0;
+
+           xsubsample = 0;
+           while (dx * (1 << xsubsample) <= 128.0) xsubsample++;
+           ysubsample = 0;
+           while (dy * (1 << ysubsample) <= 128.0) ysubsample++;
+
+           if (image->common.filter_params)
+               free (image->common.filter_params);
+
+           image->common.filter_params =
+               pixman_filter_create_separable_convolution
+               ( & image->common.n_filter_params,
+                 pixman_double_to_fixed(dx),
+                 pixman_double_to_fixed(dy),
+                 dx < 1.35 ? PIXMAN_KERNEL_LINEAR : PIXMAN_KERNEL_BOX,
+                 dy < 1.35 ? PIXMAN_KERNEL_LINEAR : PIXMAN_KERNEL_BOX,
+                 dx < 1.35 ? PIXMAN_KERNEL_IMPULSE : PIXMAN_KERNEL_BOX,
+                 dy < 1.35 ? PIXMAN_KERNEL_IMPULSE : PIXMAN_KERNEL_BOX,
+                 xsubsample, ysubsample);
+
+           flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+           break;
+
+       case PIXMAN_FILTER_BEST:
+           if (nearest_ok) {
+               flags |= (FAST_PATH_NEAREST_FILTER |
+                         FAST_PATH_NO_CONVOLUTION_FILTER);
+               break;
+           }
+           /* See notes above about filter sizes */
+           dy = 1.0 / (abs(m[2][0]) + abs(m[2][1]) + abs(m[2][2]));
+           dx = hypot (m[0][0] * dy, m[0][1] * dy);
+           dy = hypot (m[1][0] * dy, m[1][1] * dy);
+
+           /* To prevent extreme slowness this switches to BOX at
+            * 1/16 scale and stops making the filter larger at 1/24
+            * scale.
+            *
+            * When enlarging this produces normal blur up to 2x, then
+            * square pixels with a 1-pixel blurry border between them
+            * for larger sizes.  At scales larger than 128x the blur
+            * is increased to avoid making lots of subsamples.
+            */
+           if (dx > 24.0) dx = 24.0;
+           else if (dx < 1.0) {
+               if (dx >= 0.5) dx = 1.0;
+               else if (dx > 1.0/128) dx = 1.0 / (1.0 / dx - 1.0);
+               else dx = 1.0/127;
+           }
+           if (dy > 24.0) dy = 24.0;
+           else if (dy < 1.0) {
+               if (dy >= 0.5) dy = 1.0;
+               else if (dy > 1.0/128) dy = 1.0 / (1.0 / dy - 1.0);
+               else dy = 1.0/127;
            }
-       }
-       break;
 
-    case PIXMAN_FILTER_CONVOLUTION:
-       break;
+           xsubsample = 0;
+           while (dx * (1 << xsubsample) <= 128.0) xsubsample++;
+           ysubsample = 0;
+           while (dy * (1 << ysubsample) <= 128.0) ysubsample++;
+
+           image->common.filter_params =
+               pixman_filter_create_separable_convolution
+               ( & image->common.n_filter_params,
+                 pixman_double_to_fixed(dx),
+                 pixman_double_to_fixed(dy),
+                 dx < 1.0 ? PIXMAN_KERNEL_BOX : PIXMAN_KERNEL_IMPULSE,
+                 dy < 1.0 ? PIXMAN_KERNEL_BOX : PIXMAN_KERNEL_IMPULSE,
+                 dx < 16.0 ? PIXMAN_KERNEL_LANCZOS2 : PIXMAN_KERNEL_BOX,
+                 dy < 16.0 ? PIXMAN_KERNEL_LANCZOS2 : PIXMAN_KERNEL_BOX,
+                 xsubsample, ysubsample);
+
+           flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+           break;
 
-    case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
-       flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
-       break;
+       case PIXMAN_FILTER_CONVOLUTION:
+           break;
 
-    default:
-       flags |= FAST_PATH_NO_CONVOLUTION_FILTER;
-       break;
+       case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
+           flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+           break;
+
+       default:
+           flags |= FAST_PATH_NO_CONVOLUTION_FILTER;
+           break;
+       }
     }
 
     /* Repeat mode */
-- 
1.7.9.5

_______________________________________________
Pixman mailing list
Pixman@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/pixman

Reply via email to