/*
 * graph.c: Map the slowdown factor in gdk_scale_simple() when downscaling by
 * by a large ratio https://bugzilla.gnome.org/show_bug.cgi?id=80925
 * by creating a pretty 3D graph of execution time against scaling ratio.
 *
 * The slowdown starts aroung 1000-1600 area reduction factor, so
 * by default scale each axis by 1 to 50 times, giving 1 to 2500 reduction.
 *
 * $ cc -O -o graph graph.c `pkg-config --cflags --libs gtk+-2.0`
 * $ ./graph -g > test.data
 * $ gnuplot
 * gnuplot> splot "test.data"
 *
 *	Martin Guy <martinwguy@gmail.com>, Decemer 2016.
 */

#include <gdk/gdk.h>
#include <sys/time.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

/* The subpixel scaling precision from gdk_pixbuf/pixops/pixops.c,
 * used to calculate the memory usage of the filter table.
 */
#define SUBPIXEL 16

static double run_test(GdkPixbuf *sourcePixbuf, int scale_x, int scale_y);

/* We graph reducing by a factor of 50 down to 1 in each direction. */
static guint max_redux=50;
static guint min_redux=1;

static unsigned source_width=100;
static unsigned source_height=100;

/* Output gnuplot 3D plotting data instead of human-readable?
 * (see "help splot datafile" inside gnuplot)
 */
static int gnuplot = 0;

int
main(int argc, char **argv)
{
    char *filename =  (argc > 1) ? argv[1] : "image.jpg";
    GdkPixbuf *sourcePixbuf = NULL;	/* As read from a file */
    int i;


    for (i=1; i<argc; i++) {
	if (argv[i][0] == '-') switch (argv[i][1]) {
	    case 'g': gnuplot++; break;
	    case '1':
		setenv("GDK_PIXBUF_DISABLE_TWO_STEP_SCALER", "1", 0);
		break;
	    case '2':
		unsetenv("GDK_PIXBUF_DISABLE_TWO_STEP_SCALER");
		break;
	    case 's':
		if (i+1 >= argc ||
		    sscanf(argv[i+1], "%dx%d", &source_width, &source_height) != 2) {
		    fputs("Usage: -s widthxheight\n", stderr);
		    exit(1);
		}
		i++;
		break;
	    case 'r':
		if (i+1 >= argc ||
		    sscanf(argv[i+1], "%u", &max_redux) != 1) {
		    fputs("Usage: -r max_ratio\n", stderr);
		    exit(1);
		}
		i++;
		break;
	    default:
usage:		fputs("Usage: graph [-s widthxheight] [-r max_ratio]\n", stderr);
		exit(1);
	}
    }
    if (i != argc) goto usage;

    if (argc > 1 && strcmp(argv[1], "-g") == 0) gnuplot++;

    setbuf(stdout, NULL);

    gdk_init(&argc, &argv);

    /* Make pixbuf, then make image from pixbuf because
     * gtk_image_new_from_file() doesn't flag errors */
    {
	GError *error = NULL;
	sourcePixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 0, 8, source_width, source_height);
	if (sourcePixbuf == NULL) {
	    g_message("%s", error->message);
	    return 1; /* exit() */
	}
    }

    if (gnuplot) printf("# ratio_x\tratio_y\tmsec\n");
    {
      int ratio_x, ratio_y;
      for (ratio_y = min_redux; ratio_y <= max_redux; ratio_y++) {
        for (ratio_x = min_redux; ratio_x <= max_redux; ratio_x++) {
	  double msec = run_test(sourcePixbuf, ratio_x, ratio_y);
	  if (gnuplot) printf("%d\t%d\t%3f\n", ratio_x, ratio_y, msec);
        }
      }
    }

    return 0;
}

/* Time how long it takes to downscale the source image by the specified ratios.
 * The dimensions should be reduced to 1/scale_x and 1/scale_y.
 * Returns the execution time of each scale operation, in milliseconds.
 */

/* How many times to run each test, to get an average execution time and
 * so that dead time due to garbage collection of a large heap is attributed
 * more to the test runs that cause it.
 */
#define NITER 10

static double
run_test(GdkPixbuf *sourcePixbuf, int ratio_x, int ratio_y)
{
    double scale_x = 1.0 / (double) ratio_x;
    double scale_y = 1.0 / (double) ratio_y;
    guint to_width = (guint)(source_width * scale_x + 0.5);
    guint to_height = (guint)(source_height * scale_y + 0.5);
    GdkPixbuf *destPixbuf;

    struct itimerval init;
    struct itimerval b4, afta;
    int i;
    int n_x = ceil (1.0 / scale_x + 1.0);	/* For TILES & BILINEAR. */
    int n_y = ceil (1.0 / scale_y + 1.0);	/* For HYPER it is ceil (1/yscale + 3.0) */
    /* make_weights creates (n_x + n_y) x SUBSAMPLE(=16) doubles */
    /* make_filter_table creates (n_x + n_y) x SUBSAMPLE x SUBSAMPLE doubles */
    int weights = n_x + n_y;
    int filters = n_x * n_y;

    destPixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 0, 8, to_width, to_height);
    if (!destPixbuf) {
	fprintf(stderr, "Can't create destination pixbuf.\n");
	exit(1);
    }

    init.it_value.tv_sec = 86400;
    init.it_value.tv_usec = 0;
    init.it_interval.tv_sec = 86400;
    init.it_interval.tv_usec = 0;
    setitimer(ITIMER_PROF, &init, NULL);

    getitimer(ITIMER_PROF, &b4);
    for (i=0; i<NITER; i++) {
	gdk_pixbuf_scale(sourcePixbuf, destPixbuf, 0, 0, to_width, to_height,
			 0.0, 0.0, scale_x, scale_y, GDK_INTERP_BILINEAR);
    }
    getitimer(ITIMER_PROF, &afta);

    {
	double msec = ((b4.it_value.tv_sec*1000.0+b4.it_value.tv_usec/1000.0) -
	    (afta.it_value.tv_sec*1000.0+afta.it_value.tv_usec/1000.0)) / NITER;

        unsigned mem = weights * SUBPIXEL * sizeof(double) +
	   	       filters * SUBPIXEL * SUBPIXEL * sizeof(double);

	if (!gnuplot) /* Human-readable table */
            printf("%gx%g %dx%d->%dx%d weights=%d filters=%d mem=%dK, %.2f ms\n",
	        scale_x, scale_y, source_width, source_height, to_width, to_height,
	        weights, filters, (mem+512)/1024, msec);

        return msec;
    }
}
