[07/14] pixman-image: Added enable-gnuplot config to view filters in gnuplot

Submitted by Søren Sandmann on April 12, 2016, 2:36 a.m.

Details

Message ID 1460428613-25813-8-git-send-email-soren.sandmann@gmail.com
State Under Review
Series "Series without cover letter"
Headers show

Commit Message

Søren Sandmann April 12, 2016, 2:36 a.m.
From: Bill Spitzak <spitzak@gmail.com>

If enable-gnuplot is configured, then you can pipe the output of a
pixman-using program to gnuplot and get a continuously-updated plot of
the horizontal filter. This works well with demos/scale to test the
filter generation.

The plot is all the different subposition filters shuffled
together. This is misleading in a few cases:

  IMPULSE.BOX - goes up and down as the subfilters have different
                numbers of non-zero samples

  IMPULSE.TRIANGLE - somewhat crooked for the same reason

  1-wide filters - looks triangular, but a 1-wide box would be more
                   accurate

Changes by Søren: Rewrote the pixman-filter.c part to
     - make it generate correct coordinates
     - add a comment on how coordinates are generated
     - in rounding.txt, add a ceil() variant of the first-sample
       formula
     - make the gnuplot output slightly prettier

v7: First time this ability was included

v8: Use config option
    Moved code to the filter generator
    Modified scale demo to not call filter generator a second time.

v10: Only print if successful generation of plots
     Use #ifdef, not #if

v11: small whitespace fixes

Signed-off-by: Bill Spitzak <spitzak@gmail.com>
Signed-off-by: Søren Sandmann <soren.sandmann@gmail.com>
---
 configure.ac           |  13 ++++++
 pixman/pixman-filter.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++
 pixman/rounding.txt    |   1 +
 3 files changed, 129 insertions(+)

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 6b2134e..e833e45 100644
--- a/configure.ac
+++ b/configure.ac
@@ -834,6 +834,19 @@  fi
 AC_SUBST(PIXMAN_TIMERS)
 
 dnl ===================================
+dnl gnuplot
+
+AC_ARG_ENABLE(gnuplot,
+   [AC_HELP_STRING([--enable-gnuplot],
+                   [enable output of filters that can be piped to gnuplot [default=no]])],
+   [enable_gnuplot=$enableval], [enable_gnuplot=no])
+
+if test $enable_gnuplot = yes ; then
+   AC_DEFINE(PIXMAN_GNUPLOT, 1, [enable output that can be piped to gnuplot])
+fi
+AC_SUBST(PIXMAN_GNUPLOT)
+
+dnl ===================================
 dnl GTK+
 
 AC_ARG_ENABLE(gtk,
diff --git a/pixman/pixman-filter.c b/pixman/pixman-filter.c
index b2bf53f..af46a43 100644
--- a/pixman/pixman-filter.c
+++ b/pixman/pixman-filter.c
@@ -297,6 +297,117 @@  create_1d_filter (int             *width,
     return params;
 }
 
+#ifdef PIXMAN_GNUPLOT
+
+/* If enable-gnuplot is configured, then you can pipe the output of a
+ * pixman-using program to gnuplot and get a continuously-updated plot
+ * of the horizontal filter. This works well with demos/scale to test
+ * the filter generation.
+ *
+ * The plot is all the different subposition filters shuffled
+ * together. This is misleading in a few cases:
+ *
+ *  IMPULSE.BOX - goes up and down as the subfilters have different
+ *		  numbers of non-zero samples
+ *  IMPULSE.TRIANGLE - somewhat crooked for the same reason
+ *  1-wide filters - looks triangular, but a 1-wide box would be more
+ *		     accurate
+ */
+static void
+gnuplot_filter (int width, int n_phases, const pixman_fixed_t* p)
+{
+    double step;
+    int i, j;
+    int first;
+
+    step = 1.0 / n_phases;
+
+    printf ("set style line 1 lc rgb '#0060ad' lt 1 lw 0.5 pt 7 pi 1 ps 0.5\n");
+    printf ("plot '-' with linespoints ls 1\n");
+
+    /* The position of the first sample of the phase corresponding to
+     * frac is given by:
+     * 
+     *     ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
+     * 
+     * We have to find the frac that minimizes this expression.
+     * 
+     * For odd widths, we have
+     * 
+     *     ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
+     *   = ceil (frac) + K - frac
+     *   = 1 + K - frac
+     * 
+     * for some K, so this is minimized when frac is maximized and
+     * strictly growing with frac. So for odd widths, we can simply
+     * start at the last phase and go backwards.
+     * 
+     * For even widths, we have
+     * 
+     *     ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
+     *   = ceil (frac - 0.5) + K - frac
+     * 
+     * The graph for this function (ignoring K) looks like this:
+     * 
+     *        0.5
+     *           |    |\ 
+     *           |    | \ 
+     *           |    |  \ 
+     *         0 |    |   \ 
+     *           |\   |
+     *           | \  |
+     *           |  \ |
+     *      -0.5 |   \|
+     *   ---------------------------------
+     *           0    0.5   1
+     * 
+     * So in this case we need to start with the phase whose frac is
+     * less than, but as close as possible to 0.5, then go backwards
+     * until we hit the first phase, then wrap around to the last
+     * phase and continue backwards.
+     * 
+     * Which phase is as close as possible 0.5? The locations of the
+     * sampling point corresponding to the kth phase is given by
+     * 1/(2 * n_phases) + k / n_phases:
+     * 
+     *         1/(2 * n_phases) + k / n_phases = 0.5
+     *  
+     * from which it follows that
+     * 
+     *         k = (n_phases - 1) / 2
+     * 
+     * rounded down is the phase in question.
+     */
+    if (width & 1)
+	first = n_phases - 1;
+    else
+	first = (n_phases - 1) / 2;
+
+    for (j = 0; j < width; ++j)
+    {
+	for (i = 0; i < n_phases; ++i)
+	{
+	    int phase = first - i;
+	    double frac, pos;
+
+	    if (phase < 0)
+		phase = n_phases + phase;
+
+	    frac = step / 2.0 + phase * step;
+	    pos = ceil (frac - width / 2.0 - 0.5) + 0.5 - frac + j;
+
+	    printf ("%g %g\n",
+		    pos,
+		    pixman_fixed_to_double (*(p + phase * width + j)));
+	}
+    }
+
+    printf ("e\n");
+    fflush (stdout);
+}
+
+#endif
+
 /* Create the parameter list for a SEPARABLE_CONVOLUTION filter
  * with the given kernels and scale parameters
  */
@@ -346,5 +457,9 @@  out:
     free (horz);
     free (vert);
 
+#ifdef PIXMAN_GNUPLOT
+    gnuplot_filter(width, subsample_x, params + 4);
+#endif
+
     return params;
 }
diff --git a/pixman/rounding.txt b/pixman/rounding.txt
index b52b084..1c00019 100644
--- a/pixman/rounding.txt
+++ b/pixman/rounding.txt
@@ -160,6 +160,7 @@  which means the contents of the matrix corresponding to (frac) should
 contain width samplings of the function, with the first sample at:
 
        floor (frac - (width - 1) / 2.0 - e) + 0.5 - frac
+     = ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
 
 This filter is called separable because each of the k x k convolution
 matrices is specified with two k-wide vectors, one for each dimension,

Comments

Bill Spitzak April 12, 2016, 8:11 p.m.
On Mon, Apr 11, 2016 at 7:36 PM, Søren Sandmann Pedersen <
soren.sandmann@gmail.com> wrote:

> From: Bill Spitzak <spitzak@gmail.com>
>
> If enable-gnuplot is configured, then you can pipe the output of a
> pixman-using program to gnuplot and get a continuously-updated plot of
> the horizontal filter. This works well with demos/scale to test the
> filter generation.
>
> The plot is all the different subposition filters shuffled
> together. This is misleading in a few cases:
>
>   IMPULSE.BOX - goes up and down as the subfilters have different
>                 numbers of non-zero samples
>
>   IMPULSE.TRIANGLE - somewhat crooked for the same reason
>
>   1-wide filters - looks triangular, but a 1-wide box would be more
>                    accurate
>

Because you are not plotting the two dummy points at (0,±width/2), a 1-wide
filter is actually just a single point.

You may be right that leaving the dummy points off the plot may make it
easier to figure out what is going on.


> Changes by Søren: Rewrote the pixman-filter.c part to
>      - make it generate correct coordinates
>      - add a comment on how coordinates are generated
>      - in rounding.txt, add a ceil() variant of the first-sample
>        formula
>      - make the gnuplot output slightly prettier
>
> v7: First time this ability was included
>
> v8: Use config option
>     Moved code to the filter generator
>     Modified scale demo to not call filter generator a second time.
>
> v10: Only print if successful generation of plots
>      Use #ifdef, not #if
>
> v11: small whitespace fixes
>
> Signed-off-by: Bill Spitzak <spitzak@gmail.com>
> Signed-off-by: Søren Sandmann <soren.sandmann@gmail.com>
> ---
>  configure.ac           |  13 ++++++
>  pixman/pixman-filter.c | 115
> +++++++++++++++++++++++++++++++++++++++++++++++++
>  pixman/rounding.txt    |   1 +
>  3 files changed, 129 insertions(+)
>
> diff --git a/configure.ac b/configure.ac
> index 6b2134e..e833e45 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -834,6 +834,19 @@ fi
>  AC_SUBST(PIXMAN_TIMERS)
>
>  dnl ===================================
> +dnl gnuplot
> +
> +AC_ARG_ENABLE(gnuplot,
> +   [AC_HELP_STRING([--enable-gnuplot],
> +                   [enable output of filters that can be piped to gnuplot
> [default=no]])],
> +   [enable_gnuplot=$enableval], [enable_gnuplot=no])
> +
> +if test $enable_gnuplot = yes ; then
> +   AC_DEFINE(PIXMAN_GNUPLOT, 1, [enable output that can be piped to
> gnuplot])
> +fi
> +AC_SUBST(PIXMAN_GNUPLOT)
> +
> +dnl ===================================
>  dnl GTK+
>
>  AC_ARG_ENABLE(gtk,
> diff --git a/pixman/pixman-filter.c b/pixman/pixman-filter.c
> index b2bf53f..af46a43 100644
> --- a/pixman/pixman-filter.c
> +++ b/pixman/pixman-filter.c
> @@ -297,6 +297,117 @@ create_1d_filter (int             *width,
>      return params;
>  }
>
> +#ifdef PIXMAN_GNUPLOT
> +
> +/* If enable-gnuplot is configured, then you can pipe the output of a
> + * pixman-using program to gnuplot and get a continuously-updated plot
> + * of the horizontal filter. This works well with demos/scale to test
> + * the filter generation.
> + *
> + * The plot is all the different subposition filters shuffled
> + * together. This is misleading in a few cases:
> + *
> + *  IMPULSE.BOX - goes up and down as the subfilters have different
> + *               numbers of non-zero samples
> + *  IMPULSE.TRIANGLE - somewhat crooked for the same reason
> + *  1-wide filters - looks triangular, but a 1-wide box would be more
> + *                  accurate
> + */
> +static void
> +gnuplot_filter (int width, int n_phases, const pixman_fixed_t* p)
> +{
> +    double step;
> +    int i, j;
> +    int first;
> +
> +    step = 1.0 / n_phases;
> +
> +    printf ("set style line 1 lc rgb '#0060ad' lt 1 lw 0.5 pt 7 pi 1 ps
> 0.5\n");
> +    printf ("plot '-' with linespoints ls 1\n");
> +
> +    /* The position of the first sample of the phase corresponding to
> +     * frac is given by:
> +     *
> +     *     ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
> +     *
> +     * We have to find the frac that minimizes this expression.
> +     *
> +     * For odd widths, we have
> +     *
> +     *     ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
> +     *   = ceil (frac) + K - frac
> +     *   = 1 + K - frac
> +     *
> +     * for some K, so this is minimized when frac is maximized and
> +     * strictly growing with frac. So for odd widths, we can simply
> +     * start at the last phase and go backwards.
> +     *
> +     * For even widths, we have
> +     *
> +     *     ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
> +     *   = ceil (frac - 0.5) + K - frac
> +     *
> +     * The graph for this function (ignoring K) looks like this:
> +     *
> +     *        0.5
> +     *           |    |\
> +     *           |    | \
> +     *           |    |  \
> +     *         0 |    |   \
> +     *           |\   |
> +     *           | \  |
> +     *           |  \ |
> +     *      -0.5 |   \|
> +     *   ---------------------------------
> +     *           0    0.5   1
> +     *
> +     * So in this case we need to start with the phase whose frac is
> +     * less than, but as close as possible to 0.5, then go backwards
> +     * until we hit the first phase, then wrap around to the last
> +     * phase and continue backwards.
> +     *
> +     * Which phase is as close as possible 0.5? The locations of the
> +     * sampling point corresponding to the kth phase is given by
> +     * 1/(2 * n_phases) + k / n_phases:
> +     *
> +     *         1/(2 * n_phases) + k / n_phases = 0.5
> +     *
> +     * from which it follows that
> +     *
> +     *         k = (n_phases - 1) / 2
> +     *
> +     * rounded down is the phase in question.
> +     */
> +    if (width & 1)
> +       first = n_phases - 1;
> +    else
> +       first = (n_phases - 1) / 2;
>

My version printed an extra point at 0,-width/2 here. Not sure if that is
an improvement or a bug, you might want to try it just to get your opinion.


> +
> +    for (j = 0; j < width; ++j)
> +    {
> +       for (i = 0; i < n_phases; ++i)
> +       {
> +           int phase = first - i;
> +           double frac, pos;
> +
> +           if (phase < 0)
> +               phase = n_phases + phase;
> +
> +           frac = step / 2.0 + phase * step;
> +           pos = ceil (frac - width / 2.0 - 0.5) + 0.5 - frac + j;
>

See below I think this has to be changed to the floor() version.


> +
> +           printf ("%g %g\n",
> +                   pos,
> +                   pixman_fixed_to_double (*(p + phase * width + j)));
> +       }
> +    }
>

My version printed an extra point at 0,width/2 here.


> +
> +    printf ("e\n");
> +    fflush (stdout);
> +}
> +
> +#endif
> +
>  /* Create the parameter list for a SEPARABLE_CONVOLUTION filter
>   * with the given kernels and scale parameters
>   */
> @@ -346,5 +457,9 @@ out:
>      free (horz);
>      free (vert);
>
> +#ifdef PIXMAN_GNUPLOT
> +    gnuplot_filter(width, subsample_x, params + 4);
> +#endif
> +
>      return params;
>  }
> diff --git a/pixman/rounding.txt b/pixman/rounding.txt
> index b52b084..1c00019 100644
> --- a/pixman/rounding.txt
> +++ b/pixman/rounding.txt
> @@ -160,6 +160,7 @@ which means the contents of the matrix corresponding
> to (frac) should
>  contain width samplings of the function, with the first sample at:
>
>         floor (frac - (width - 1) / 2.0 - e) + 0.5 - frac
> +     = ceil (frac - width / 2.0 - 0.5) + 0.5 - frac
>

Unfortunately this produces numbers that don't agree with the filter
generator or filtering code.

For a width==4 filter with n_phases==1, the generator seems to produce
values at -1, 0, 1, 2, so the first sample is at -1. It also appears the
filtering sampler is using the same rule, otherwise these filters would
produce an obvious shift in the image.

    frac = step/2 = .5
    width = 3
    floor (frac - (width - 1) / 2.0) + 0.5 - frac = floor(.5 - 1.5) +
0.5-0.5 = floor(-1) = -1.
    ceil (frac - width / 2.0 - 0.5) + 0.5 - frac = ceil(.5 - 2.5) + 0.5-0.5
= ceil(-2) = -2

Whether by accident or not it looks like the correct formula is the floor
one with the epsilon (e) removed, in disagreement with your rounding.txt
document. I would recommend using that rather than changing the
interpretation of the filter matrix.