/* 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 */