/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* Marco gradient rendering */ /* * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "gradient.h" #include "util.h" #include #include /* This is all Alfredo's and Dan's usual very nice WindowMaker code, * slightly GTK-ized */ static GdkPixbuf* meta_gradient_create_horizontal (int width, int height, const GdkRGBA *from, const GdkRGBA *to); static GdkPixbuf* meta_gradient_create_vertical (int width, int height, const GdkRGBA *from, const GdkRGBA *to); static GdkPixbuf* meta_gradient_create_diagonal (int width, int height, const GdkRGBA *from, const GdkRGBA *to); static GdkPixbuf* meta_gradient_create_multi_horizontal (int width, int height, const GdkRGBA *colors, int count); static GdkPixbuf* meta_gradient_create_multi_vertical (int width, int height, const GdkRGBA *colors, int count); static GdkPixbuf* meta_gradient_create_multi_diagonal (int width, int height, const GdkRGBA *colors, int count); /* Used as the destroy notification function for gdk_pixbuf_new() */ static void free_buffer (guchar *pixels, gpointer data) { g_free (pixels); } static GdkPixbuf* blank_pixbuf (int width, int height, gboolean no_padding) { guchar *buf; int rowstride; g_return_val_if_fail (width > 0, NULL); g_return_val_if_fail (height > 0, NULL); if (no_padding) rowstride = width * 3; else /* Always align rows to 32-bit boundaries */ rowstride = 4 * ((3 * width + 3) / 4); buf = g_try_malloc (height * rowstride); if (!buf) return NULL; return gdk_pixbuf_new_from_data (buf, GDK_COLORSPACE_RGB, FALSE, 8, width, height, rowstride, free_buffer, NULL); } GdkPixbuf* meta_gradient_create_simple (int width, int height, const GdkRGBA *from, const GdkRGBA *to, MetaGradientType style) { switch (style) { case META_GRADIENT_HORIZONTAL: return meta_gradient_create_horizontal (width, height, from, to); case META_GRADIENT_VERTICAL: return meta_gradient_create_vertical (width, height, from, to); case META_GRADIENT_DIAGONAL: return meta_gradient_create_diagonal (width, height, from, to); case META_GRADIENT_LAST: break; } g_assert_not_reached (); return NULL; } GdkPixbuf* meta_gradient_create_multi (int width, int height, const GdkRGBA *colors, int n_colors, MetaGradientType style) { if (n_colors > 2) { switch (style) { case META_GRADIENT_HORIZONTAL: return meta_gradient_create_multi_horizontal (width, height, colors, n_colors); case META_GRADIENT_VERTICAL: return meta_gradient_create_multi_vertical (width, height, colors, n_colors); case META_GRADIENT_DIAGONAL: return meta_gradient_create_multi_diagonal (width, height, colors, n_colors); case META_GRADIENT_LAST: g_assert_not_reached (); break; } } else if (n_colors > 1) { return meta_gradient_create_simple (width, height, &colors[0], &colors[1], style); } else if (n_colors > 0) { return meta_gradient_create_simple (width, height, &colors[0], &colors[0], style); } g_assert_not_reached (); return NULL; } /* Interwoven essentially means we have two vertical gradients, * cut into horizontal strips of the given thickness, and then the strips * are alternated. I'm not sure what it's good for, just copied since * WindowMaker had it. */ GdkPixbuf* meta_gradient_create_interwoven (int width, int height, const GdkRGBA colors1[2], int thickness1, const GdkRGBA colors2[2], int thickness2) { int i, j, k, l, ll; long r1, g1, b1, dr1, dg1, db1; long r2, g2, b2, dr2, dg2, db2; GdkPixbuf *pixbuf; unsigned char *ptr; unsigned char *pixels; int rowstride; pixbuf = blank_pixbuf (width, height, FALSE); if (pixbuf == NULL) return NULL; pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); r1 = (long)(colors1[0].red*0xffffff); g1 = (long)(colors1[0].green*0xffffff); b1 = (long)(colors1[0].blue*0xffffff); r2 = (long)(colors2[0].red*0xffffff); g2 = (long)(colors2[0].green*0xffffff); b2 = (long)(colors2[0].blue*0xffffff); dr1 = ((colors1[1].red-colors1[0].red)*0xffffff)/(int)height; dg1 = ((colors1[1].green-colors1[0].green)*0xffffff)/(int)height; db1 = ((colors1[1].blue-colors1[0].blue)*0xffffff)/(int)height; dr2 = ((colors2[1].red-colors2[0].red)*0xffffff)/(int)height; dg2 = ((colors2[1].green-colors2[0].green)*0xffffff)/(int)height; db2 = ((colors2[1].blue-colors2[0].blue)*0xffffff)/(int)height; for (i=0,k=0,l=0,ll=thickness1; i>16); ptr[1] = (unsigned char) (g1>>16); ptr[2] = (unsigned char) (b1>>16); } else { ptr[0] = (unsigned char) (r2>>16); ptr[1] = (unsigned char) (g2>>16); ptr[2] = (unsigned char) (b2>>16); } for (j=1; j <= width/2; j *= 2) memcpy (&(ptr[j*3]), ptr, j*3); memcpy (&(ptr[j*3]), ptr, (width - j)*3); if (++l == ll) { if (k == 0) { k = 1; ll = thickness2; } else { k = 0; ll = thickness1; } l = 0; } r1+=dr1; g1+=dg1; b1+=db1; r2+=dr2; g2+=dg2; b2+=db2; } return pixbuf; } /* *---------------------------------------------------------------------- * meta_gradient_create_horizontal-- * Renders a horizontal linear gradient of the specified size in the * GdkPixbuf format with a border of the specified type. * * Returns: * A 24bit GdkPixbuf with the gradient (no alpha channel). * * Side effects: * None *---------------------------------------------------------------------- */ static GdkPixbuf* meta_gradient_create_horizontal (int width, int height, const GdkRGBA *from, const GdkRGBA *to) { int i; long r, g, b, dr, dg, db; GdkPixbuf *pixbuf; unsigned char *ptr; unsigned char *pixels; int r0, g0, b0; int rf, gf, bf; int rowstride; pixbuf = blank_pixbuf (width, height, FALSE); if (pixbuf == NULL) return NULL; pixels = gdk_pixbuf_get_pixels (pixbuf); ptr = pixels; rowstride = gdk_pixbuf_get_rowstride (pixbuf); r0 = (guchar) (from->red * 0xff); g0 = (guchar) (from->green * 0xff); b0 = (guchar) (from->blue * 0xff); rf = (guchar) (to->red * 0xff); gf = (guchar) (to->green * 0xff); bf = (guchar) (to->blue * 0xff); r = r0 << 16; g = g0 << 16; b = b0 << 16; dr = ((rf-r0)<<16)/(int)width; dg = ((gf-g0)<<16)/(int)width; db = ((bf-b0)<<16)/(int)width; /* render the first line */ for (i=0; i>16); *(ptr++) = (unsigned char)(g>>16); *(ptr++) = (unsigned char)(b>>16); r += dr; g += dg; b += db; } /* copy the first line to the other lines */ for (i=1; ired * 0xff); g0 = (guchar) (from->green * 0xff); b0 = (guchar) (from->blue * 0xff); rf = (guchar) (to->red * 0xff); gf = (guchar) (to->green * 0xff); bf = (guchar) (to->blue * 0xff); r = r0<<16; g = g0<<16; b = b0<<16; dr = ((rf-r0)<<16)/(int)height; dg = ((gf-g0)<<16)/(int)height; db = ((bf-b0)<<16)/(int)height; for (i=0; i>16); ptr[1] = (unsigned char)(g>>16); ptr[2] = (unsigned char)(b>>16); for (j=1; j <= width/2; j *= 2) memcpy (&(ptr[j*3]), ptr, j*3); memcpy (&(ptr[j*3]), ptr, (width - j)*3); r+=dr; g+=dg; b+=db; } return pixbuf; } /* *---------------------------------------------------------------------- * meta_gradient_create_diagonal-- * Renders a diagonal linear gradient of the specified size in the * GdkPixbuf format with a border of the specified type. * * Returns: * A 24bit GdkPixbuf with the gradient (no alpha channel). * * Side effects: * None *---------------------------------------------------------------------- */ static GdkPixbuf* meta_gradient_create_diagonal (int width, int height, const GdkRGBA *from, const GdkRGBA *to) { GdkPixbuf *pixbuf, *tmp; int j; float a, offset; unsigned char *ptr; unsigned char *pixels; int rowstride; if (width == 1) return meta_gradient_create_vertical (width, height, from, to); else if (height == 1) return meta_gradient_create_horizontal (width, height, from, to); pixbuf = blank_pixbuf (width, height, FALSE); if (pixbuf == NULL) return NULL; pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); tmp = meta_gradient_create_horizontal (2*width-1, 1, from, to); if (!tmp) { g_object_unref (G_OBJECT (pixbuf)); return NULL; } ptr = gdk_pixbuf_get_pixels (tmp); a = ((float)(width - 1))/((float)(height - 1)); width = width * 3; /* copy the first line to the other lines with corresponding offset */ for (j=0, offset=0.0; j 2, NULL); pixbuf = blank_pixbuf (width, height, FALSE); if (pixbuf == NULL) return NULL; pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); ptr = pixels; if (count > width) count = width; if (count > 1) width2 = width/(count-1); else width2 = width; k = 0; r = (long)(colors[0].red * 0xffffff); g = (long)(colors[0].green * 0xffffff); b = (long)(colors[0].blue * 0xffffff); /* render the first line */ for (i=1; i>16); *ptr++ = (unsigned char)(g>>16); *ptr++ = (unsigned char)(b>>16); r += dr; g += dg; b += db; k++; } r = (long)(colors[i].red * 0xffffff); g = (long)(colors[i].green * 0xffffff); b = (long)(colors[i].blue * 0xffffff); } for (j=k; j>16); *ptr++ = (unsigned char)(g>>16); *ptr++ = (unsigned char)(b>>16); } /* copy the first line to the other lines */ for (i=1; i 2, NULL); pixbuf = blank_pixbuf (width, height, FALSE); if (pixbuf == NULL) return NULL; pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); ptr = pixels; if (count > height) count = height; if (count > 1) height2 = height/(count-1); else height2 = height; k = 0; r = (long)(colors[0].red * 0xffffff); g = (long)(colors[0].green * 0xffffff); b = (long)(colors[0].blue * 0xffffff); for (i=1; i>16); ptr[1] = (unsigned char)(g>>16); ptr[2] = (unsigned char)(b>>16); for (x=1; x <= width/2; x *= 2) memcpy (&(ptr[x*3]), ptr, x*3); memcpy (&(ptr[x*3]), ptr, (width - x)*3); ptr += rowstride; r += dr; g += dg; b += db; k++; } r = (long)(colors[i].red * 0xffffff); g = (long)(colors[i].green * 0xffffff); b = (long)(colors[i].blue * 0xffffff); } if (k>16); ptr[1] = (unsigned char) (g>>16); ptr[2] = (unsigned char) (b>>16); for (x=1; x <= width/2; x *= 2) memcpy (&(ptr[x*3]), ptr, x*3); memcpy (&(ptr[x*3]), ptr, (width - x)*3); ptr += rowstride; for (j=k+1; j 2, NULL); if (width == 1) return meta_gradient_create_multi_vertical (width, height, colors, count); else if (height == 1) return meta_gradient_create_multi_horizontal (width, height, colors, count); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height); if (pixbuf == NULL) return NULL; pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); if (count > width) count = width; if (count > height) count = height; if (count > 2) tmp = meta_gradient_create_multi_horizontal (2*width-1, 1, colors, count); else /* wrlib multiplies these colors by 256 before passing them in, but * I think it's a bug in wrlib, so changed here. I could be wrong * though, if we notice two-color multi diagonals not working. */ tmp = meta_gradient_create_horizontal (2*width-1, 1, &colors[0], &colors[1]); if (!tmp) { g_object_unref (G_OBJECT (pixbuf)); return NULL; } ptr = gdk_pixbuf_get_pixels (tmp); a = ((float)(width - 1))/((float)(height - 1)); width = width * 3; /* copy the first line to the other lines with corresponding offset */ for (j=0, offset=0; j 0); if (n_alphas == 1) { /* Optimize this */ simple_multiply_alpha (pixbuf, alphas[0]); return; } width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); gradient = g_new (unsigned char, width); gradient_end = gradient + width; if (n_alphas > width) n_alphas = width; if (n_alphas > 1) width2 = width / (n_alphas - 1); else width2 = width; a = alphas[0] << 8; gradient_p = gradient; /* render the gradient into an array */ for (i = 1; i < n_alphas; i++) { da = (((int)(alphas[i] - (int) alphas[i-1])) << 8) / (int) width2; for (j = 0; j < width2; j++) { *gradient_p++ = (a >> 8); a += da; } a = alphas[i] << 8; } /* get leftover pixels */ while (gradient_p != gradient_end) { *gradient_p++ = a >> 8; } /* Now for each line of the pixbuf, fill in with the gradient */ pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); p = pixels; i = 0; while (i < height) { unsigned char *row_end = p + rowstride; gradient_p = gradient; p += 3; while (gradient_p != gradient_end) { /* multiply the two alpha channels. not sure this is right. * but some end cases are that if the pixbuf contains 255, * then it should be modified to contain "alpha"; if the * pixbuf contains 0, it should remain 0. */ /* ((*p / 255.0) * (alpha / 255.0)) * 255; */ *p = (guchar) (((int) *p * (int) *gradient_p) / (int) 255); p += 4; ++gradient_p; } p = row_end; ++i; } g_free (gradient); } void meta_gradient_add_alpha (GdkPixbuf *pixbuf, const guchar *alphas, int n_alphas, MetaGradientType type) { g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); g_return_if_fail (gdk_pixbuf_get_has_alpha (pixbuf)); g_return_if_fail (n_alphas > 0); switch (type) { case META_GRADIENT_HORIZONTAL: meta_gradient_add_alpha_horizontal (pixbuf, alphas, n_alphas); break; case META_GRADIENT_VERTICAL: g_printerr ("marco: vertical alpha channel gradient not implemented yet\n"); break; case META_GRADIENT_DIAGONAL: g_printerr ("marco: diagonal alpha channel gradient not implemented yet\n"); break; case META_GRADIENT_LAST: g_assert_not_reached (); break; } }