diff options
author | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
commit | d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch) | |
tree | cbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /hacks/webcollage-helper.c | |
download | xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip |
Original 5.40
Diffstat (limited to 'hacks/webcollage-helper.c')
-rw-r--r-- | hacks/webcollage-helper.c | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/hacks/webcollage-helper.c b/hacks/webcollage-helper.c new file mode 100644 index 0000000..46ec608 --- /dev/null +++ b/hacks/webcollage-helper.c @@ -0,0 +1,592 @@ +/* webcollage-helper --- scales and pastes one image into another + * xscreensaver, Copyright (c) 2002-2017 Jamie Zawinski <jwz@jwz.org> + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +/* This is the GDK + JPEGlib implementation. See webcollage-helper-cocoa.m + for the Cocoa implementation. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(HAVE_GDK_PIXBUF) && defined(HAVE_JPEGLIB) /* whole file */ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> + +#undef HAVE_STDLIB_H /* stupid jconfig.h! */ +#include <jpeglib.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + + +char *progname; +static int verbose_p = 0; + +static void add_jpeg_comment (struct jpeg_compress_struct *cinfo); +static void write_pixbuf (GdkPixbuf *pb, const char *file); + +static GdkPixbuf * +load_pixbuf (const char *file) +{ + GdkPixbuf *pb; +#ifdef HAVE_GTK2 + GError *err = NULL; + + pb = gdk_pixbuf_new_from_file (file, &err); +#else /* !HAVE_GTK2 */ + pb = gdk_pixbuf_new_from_file (file); +#endif /* HAVE_GTK2 */ + + if (!pb) + { +#ifdef HAVE_GTK2 + fprintf (stderr, "%s: %s\n", progname, err->message); + g_error_free (err); +#else /* !HAVE_GTK2 */ + fprintf (stderr, "%s: unable to load %s\n", progname, file); +#endif /* !HAVE_GTK2 */ + exit (1); + } + + return pb; +} + + +static void +bevel_image (GdkPixbuf **pbP, int bevel_pct, + int x, int y, int w, int h) +{ + GdkPixbuf *pb = *pbP; + int small_size = (w > h ? h : w); + + int bevel_size = small_size * (bevel_pct / 100.0); + + /* Use a proportionally larger bevel size for especially small images. */ + if (bevel_size < 20 && small_size > 40) bevel_size = 20; + else if (bevel_size < 10 && small_size > 20) bevel_size = 10; + else if (bevel_size < 5) /* too small to bother bevelling */ + return; + + /* Ensure the pixbuf has an alpha channel. */ + if (! gdk_pixbuf_get_has_alpha (pb)) + { + GdkPixbuf *pb2 = gdk_pixbuf_add_alpha (pb, FALSE, 0, 0, 0); + g_object_unref (pb); + pb = pb2; + } + + { + guchar *data = gdk_pixbuf_get_pixels (pb); + guchar *line; + int rs = gdk_pixbuf_get_rowstride (pb); + int ch = gdk_pixbuf_get_n_channels (pb); + int xx, yy; + double *ramp = (double *) malloc (sizeof(*ramp) * (bevel_size + 1)); + + if (!ramp) + { + fprintf (stderr, "%s: out of memory (%d)\n", progname, bevel_size); + exit (1); + } + + for (xx = 0; xx <= bevel_size; xx++) + { + +# if 0 /* linear */ + ramp[xx] = xx / (double) bevel_size; + +# else /* sinusoidal */ + double p = (xx / (double) bevel_size); + double s = sin (p * M_PI / 2); + ramp[xx] = s; +# endif + } + + line = data + (rs * y); + for (yy = 0; yy < h; yy++) + { + guchar *p = line + (x * ch); + for (xx = 0; xx < w; xx++) + { + double rx, ry, r; + + if (xx < bevel_size) rx = ramp[xx]; + else if (xx >= w - bevel_size) rx = ramp[w - xx - 1]; + else rx = 1; + + if (yy < bevel_size) ry = ramp[yy]; + else if (yy >= h - bevel_size) ry = ramp[h - yy - 1]; + else ry = 1; + + r = rx * ry; + if (r != 1) + p[ch-1] *= r; + + p += ch; + } + line += rs; + } + +#if 0 /* show the ramp */ + line = data + (rs * y); + for (yy = 0; yy < h; yy++) + { + guchar *p = line + (x * ch); + for (xx = 0; xx < w; xx++) + { + int cc = 0; + for (cc = 0; cc < ch-1; cc++) + p[cc] = 255; + p += ch; + } + line += rs; + } +#endif + + free (ramp); + + if (verbose_p) + fprintf (stderr, "%s: added %d%% bevel (%d px)\n", progname, + bevel_pct, bevel_size); + } + + *pbP = pb; +} + + +static void +paste (const char *paste_file, + const char *base_file, + double from_scale, + double opacity, int bevel_pct, + int from_x, int from_y, int to_x, int to_y, + int w, int h) +{ + GdkPixbuf *paste_pb; + GdkPixbuf *base_pb; + + int paste_w, paste_h; + int base_w, base_h; + + paste_pb = load_pixbuf (paste_file); + base_pb = load_pixbuf (base_file); + + paste_w = gdk_pixbuf_get_width (paste_pb); + paste_h = gdk_pixbuf_get_height (paste_pb); + + base_w = gdk_pixbuf_get_width (base_pb); + base_h = gdk_pixbuf_get_height (base_pb); + + if (verbose_p) + { + fprintf (stderr, "%s: loaded %s: %dx%d\n", + progname, base_file, base_w, base_h); + fprintf (stderr, "%s: loaded %s: %dx%d\n", + progname, paste_file, paste_w, paste_h); + } + + if (from_scale != 1.0) + { + int new_w = paste_w * from_scale; + int new_h = paste_h * from_scale; + GdkPixbuf *new_pb = gdk_pixbuf_scale_simple (paste_pb, new_w, new_h, + GDK_INTERP_HYPER); + g_object_unref (paste_pb); + paste_pb = new_pb; + paste_w = gdk_pixbuf_get_width (paste_pb); + paste_h = gdk_pixbuf_get_height (paste_pb); + + if (verbose_p) + fprintf (stderr, "%s: %s: scaled to %dx%d (%.2f)\n", + progname, paste_file, paste_w, paste_h, from_scale); + } + + if (w == 0) w = paste_w - from_x; + if (h == 0) h = paste_h - from_y; + + { + int ofx = from_x; + int ofy = from_y; + int otx = to_x; + int oty = to_y; + int ow = w; + int oh = h; + + int clipped = 0; + + if (from_x < 0) /* from left out of bounds */ + { + w += from_x; + from_x = 0; + clipped = 1; + } + + if (from_y < 0) /* from top out of bounds */ + { + h += from_y; + from_y = 0; + clipped = 1; + } + + if (to_x < 0) /* to left out of bounds */ + { + w += to_x; + from_x -= to_x; + to_x = 0; + clipped = 1; + } + + if (to_y < 0) /* to top out of bounds */ + { + h += to_y; + from_y -= to_y; + to_y = 0; + clipped = 1; + } + + if (from_x + w > paste_w) /* from right out of bounds */ + { + w = paste_w - from_x; + clipped = 1; + } + + if (from_y + h > paste_h) /* from bottom out of bounds */ + { + h = paste_h - from_y; + clipped = 1; + } + + if (to_x + w > base_w) /* to right out of bounds */ + { + w = base_w - to_x; + clipped = 1; + } + + if (to_y + h > base_h) /* to bottom out of bounds */ + { + h = base_h - to_y; + clipped = 1; + } + + + if (clipped && verbose_p) + { + fprintf (stderr, "clipped from: %dx%d %d,%d %d,%d\n", + ow, oh, ofx, ofy, otx, oty); + fprintf (stderr, "clipped to: %dx%d %d,%d %d,%d\n", + w, h, from_x, from_y, to_x, to_y); + } + } + + if (bevel_pct > 0) + bevel_image (&paste_pb, bevel_pct, + from_x, from_y, w, h); + + if (opacity == 1.0 && bevel_pct == 0) + gdk_pixbuf_copy_area (paste_pb, + from_x, from_y, w, h, + base_pb, + to_x, to_y); + else + { + from_x++; /* gdk_pixbuf_composite gets confused about the bevel: */ + from_y++; /* it leaves a stripe on the top and left if we try to */ + to_x++; /* start at 0,0, so pull it right and down by 1 pixel. */ + to_y++; /* (problem seen in gtk2-2.4.14-2.fc3) */ + w--; + h--; + + if (w > 0 && h > 0) + gdk_pixbuf_composite (paste_pb, base_pb, + to_x, to_y, w, h, + to_x - from_x, to_y - from_y, + 1.0, 1.0, + GDK_INTERP_HYPER, + opacity * 255); + } + + if (verbose_p) + fprintf (stderr, "%s: pasted %dx%d from %d,%d to %d,%d\n", + progname, paste_w, paste_h, from_x, from_y, to_x, to_y); + + g_object_unref (paste_pb); + write_pixbuf (base_pb, base_file); + g_object_unref (base_pb); +} + + +static guint32 +parse_color (const char *s) +{ + static const char hex[128] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, + 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + unsigned char r=0, g=0, b=0; + + if (!strcasecmp (s, "black")) ; + else if (!strcasecmp (s, "white")) r = g = b = 0xFF; + else if (!strcasecmp (s, "red")) r = 0xFF; + else if (!strcasecmp (s, "green")) g = 0xFF; + else if (!strcasecmp (s, "blue")) b = 0xFF; + else + { + if (*s != '#' || strlen(s) != 7) + { + fprintf (stderr, "%s: unparsable color: \"%s\"\n", progname, s); + exit (1); + } + s++; + r = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2; + g = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2; + b = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2; + } + + return (r << 16) | (g << 8) | b; +} + + +static void +create (const char *color, + int w, int h, + const char *file) +{ + int i; + GdkPixbuf *pb; + guint32 pixel = parse_color (color); + unsigned char *bytes = malloc (w * h * 3); + if (!bytes) abort(); + for (i = 0; i < w * h * 3; i += 3) + { + bytes[i] = 0xFF & (pixel >> 16); + bytes[i+1] = 0xFF & (pixel >> 8); + bytes[i+2] = 0xFF & (pixel); + } + + pb = gdk_pixbuf_new_from_data (bytes, GDK_COLORSPACE_RGB, + FALSE, 8, /* alpha, sample size */ + w, h, + w * 3, /* rowstride */ + NULL, 0); + if (!pb) abort(); + write_pixbuf (pb, file); + g_object_unref (pb); + free (bytes); +} + + +static void +write_pixbuf (GdkPixbuf *pb, const char *file) +{ + int jpeg_quality = 85; + + int w = gdk_pixbuf_get_width (pb); + int h = gdk_pixbuf_get_height (pb); + guchar *data = gdk_pixbuf_get_pixels (pb); + int ww = gdk_pixbuf_get_rowstride (pb); + int channels = gdk_pixbuf_get_n_channels (pb); + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + FILE *out; + + if (channels != 3) + { + fprintf (stderr, "%s: %d channels?\n", progname, channels); + exit (1); + } + + cinfo.err = jpeg_std_error (&jerr); + jpeg_create_compress (&cinfo); + + out = fopen (file, "wb"); + if (!out) + { + char buf[255]; + sprintf (buf, "%.100s: %.100s", progname, file); + perror (buf); + exit (1); + } + else if (verbose_p) + fprintf (stderr, "%s: writing %s...", progname, file); + + jpeg_stdio_dest (&cinfo, out); + + cinfo.image_width = w; + cinfo.image_height = h; + cinfo.input_components = channels; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults (&cinfo); + jpeg_simple_progression (&cinfo); + jpeg_set_quality (&cinfo, jpeg_quality, TRUE); + + jpeg_start_compress (&cinfo, TRUE); + add_jpeg_comment (&cinfo); + + { + guchar *d = data; + guchar *end = d + (ww * h); + while (d < end) + { + jpeg_write_scanlines (&cinfo, &d, 1); + d += ww; + } + } + + jpeg_finish_compress (&cinfo); + jpeg_destroy_compress (&cinfo); + + if (verbose_p) + { + struct stat st; + fflush (out); + if (fstat (fileno (out), &st)) + { + char buf[255]; + sprintf (buf, "%.100s: %.100s", progname, file); + perror (buf); + exit (1); + } + fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024); + } + + fclose (out); +} + + +static void +add_jpeg_comment (struct jpeg_compress_struct *cinfo) +{ + time_t now = time ((time_t *) 0); + struct tm *tm = localtime (&now); + const char fmt[] = + "\r\n" + " Generated by WebCollage: Exterminate All Rational Thought. \r\n" + " Copyright (c) 1999-%Y by Jamie Zawinski <jwz@jwz.org> \r\n" + "\r\n" + " https://www.jwz.org/webcollage/ \r\n" + "\r\n" + " This is what the web looked like on %d %b %Y at %I:%M:%S %p %Z. \r\n" + "\r\n"; + char comment[sizeof(fmt) + 100]; + strftime (comment, sizeof(comment)-1, fmt, tm); + jpeg_write_marker (cinfo, JPEG_COM, + (unsigned char *) comment, + strlen (comment)); +} + + +static void +usage (void) +{ + fprintf (stderr, + "\nusage: %s [-v] paste-file base-file\n" + "\t from-scale opacity\n" + "\t from-x from-y to-x to-y w h\n" + "\n" + "\t Pastes paste-file into base-file.\n" + "\t base-file will be overwritten (with JPEG data.)\n" + "\t scaling is applied first: coordinates apply to scaled image.\n" + "\n" + "usage: %s [-v] color width height output-file\n" + "\t Creates a new image of a solid color.\n\n", + progname, progname); + exit (1); +} + + +int +main (int argc, char **argv) +{ + int i; + char *paste_file, *base_file, *s, dummy; + double from_scale, opacity; + int from_x, from_y, to_x, to_y, w, h, bevel_pct; + + i = 0; + progname = argv[i++]; + s = strrchr (progname, '/'); + if (s) progname = s+1; + + if (!strcmp(argv[i], "-v")) + verbose_p++, i++; + + if (argc == 11 || argc == 12) + { + paste_file = argv[i++]; + base_file = argv[i++]; + + if (*paste_file == '-') usage(); + if (*base_file == '-') usage(); + + s = argv[i++]; + if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage(); + if (from_scale <= 0 || from_scale > 100) usage(); + + s = argv[i++]; + if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage(); + if (opacity <= 0 || opacity > 1) usage(); + + s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage(); + s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage(); + s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage(); + s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage(); + s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage(); + s = argv[i]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage(); + + bevel_pct = 10; /* #### */ + + if (w < 0) usage(); + if (h < 0) usage(); + +# ifdef HAVE_GTK2 +# if !GLIB_CHECK_VERSION(2, 36 ,0) + g_type_init (); +# endif +# endif /* HAVE_GTK2 */ + + paste (paste_file, base_file, + from_scale, opacity, bevel_pct, + from_x, from_y, to_x, to_y, + w, h); + } + else if (argc == 4 || argc == 5) + { + char *color = argv[i++]; + s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage(); + s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage(); + paste_file = argv[i++]; + if (*paste_file == '-') usage(); + + create (color, w, h, paste_file); + } + else + { + usage(); + } + + exit (0); +} + +#endif /* HAVE_GDK_PIXBUF && HAVE_JPEGLIB -- whole file */ |