/* webcollage-helper --- scales and pastes one image into another * xscreensaver, Copyright (c) 2002-2017 Jamie Zawinski * * 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 #include #include #include #include #include #include #undef HAVE_STDLIB_H /* stupid jconfig.h! */ #include #include 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 \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 */