2 * Copyright (C) 1998-2000 Red Hat, Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 * Written by Owen Taylor <otaylor@redhat.com>, based on code by
20 * Carsten Haitzler <raster@rasterman.com>
26 #include <gdk-pixbuf/gdk-pixbuf.h>
28 static GCache *pixbuf_cache = NULL;
31 bilinear_gradient (GdkPixbuf *src,
37 guint n_channels = gdk_pixbuf_get_n_channels (src);
38 guint src_rowstride = gdk_pixbuf_get_rowstride (src);
39 guchar *src_pixels = gdk_pixbuf_get_pixels (src);
40 guchar *p1, *p2, *p3, *p4;
46 if (src_x == 0 || src_y == 0)
48 g_warning ("invalid source position for bilinear gradient");
52 p1 = src_pixels + (src_y - 1) * src_rowstride + (src_x - 1) * n_channels;
54 p3 = src_pixels + src_y * src_rowstride + (src_x - 1) * n_channels;
57 result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
62 g_warning ("failed to create a %dx%d pixbuf", width, height);
66 dest_rowstride = gdk_pixbuf_get_rowstride (result);
67 dest_pixels = gdk_pixbuf_get_pixels (result);
69 for (i = 0; i < height; i++)
71 guchar *p = dest_pixels + dest_rowstride *i;
75 for (k = 0; k < n_channels; k++)
77 guint start = ((height - i) * p1[k] + (1 + i) * p3[k]) / (height + 1);
78 guint end = ((height - i) * p2[k] + (1 + i) * p4[k]) / (height + 1);
80 dv[k] = (((gint)end - (gint)start) << 16) / (width + 1);
81 v[k] = (start << 16) + dv[k] + 0x8000;
84 for (j = width; j; j--)
86 for (k = 0; k < n_channels; k++)
98 horizontal_gradient (GdkPixbuf *src,
104 guint n_channels = gdk_pixbuf_get_n_channels (src);
105 guint src_rowstride = gdk_pixbuf_get_rowstride (src);
106 guchar *src_pixels = gdk_pixbuf_get_pixels (src);
107 guint dest_rowstride;
114 g_warning ("invalid source position for horizontal gradient");
118 result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
123 g_warning ("failed to create a %dx%d pixbuf", width, height);
127 dest_rowstride = gdk_pixbuf_get_rowstride (result);
128 dest_pixels = gdk_pixbuf_get_pixels (result);
130 for (i = 0; i < height; i++)
132 guchar *p = dest_pixels + dest_rowstride *i;
133 guchar *p1 = src_pixels + (src_y + i) * src_rowstride + (src_x - 1) * n_channels;
134 guchar *p2 = p1 + n_channels;
139 for (k = 0; k < n_channels; k++)
141 dv[k] = (((gint)p2[k] - (gint)p1[k]) << 16) / (width + 1);
142 v[k] = (p1[k] << 16) + dv[k] + 0x8000;
145 for (j = width; j; j--)
147 for (k = 0; k < n_channels; k++)
159 vertical_gradient (GdkPixbuf *src,
165 guint n_channels = gdk_pixbuf_get_n_channels (src);
166 guint src_rowstride = gdk_pixbuf_get_rowstride (src);
167 guchar *src_pixels = gdk_pixbuf_get_pixels (src);
168 guchar *top_pixels, *bottom_pixels;
169 guint dest_rowstride;
176 g_warning ("invalid source position for vertical gradient");
180 top_pixels = src_pixels + (src_y - 1) * src_rowstride + (src_x) * n_channels;
181 bottom_pixels = top_pixels + src_rowstride;
183 result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
188 g_warning ("failed to create a %dx%d pixbuf", width, height);
192 dest_rowstride = gdk_pixbuf_get_rowstride (result);
193 dest_pixels = gdk_pixbuf_get_pixels (result);
195 for (i = 0; i < height; i++)
197 guchar *p = dest_pixels + dest_rowstride *i;
198 guchar *p1 = top_pixels;
199 guchar *p2 = bottom_pixels;
201 for (j = width * n_channels; j; j--)
202 *(p++) = ((height - i) * *(p1++) + (1 + i) * *(p2++)) / (height + 1);
209 replicate_single (GdkPixbuf *src,
215 guint n_channels = gdk_pixbuf_get_n_channels (src);
216 guchar *pixels = (gdk_pixbuf_get_pixels (src) +
217 src_y * gdk_pixbuf_get_rowstride (src) +
219 guchar r = *(pixels++);
220 guchar g = *(pixels++);
221 guchar b = *(pixels++);
222 guint dest_rowstride;
231 result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
236 g_warning ("failed to create a %dx%d pixbuf", width, height);
240 dest_rowstride = gdk_pixbuf_get_rowstride (result);
241 dest_pixels = gdk_pixbuf_get_pixels (result);
243 for (i = 0; i < height; i++)
245 guchar *p = dest_pixels + dest_rowstride *i;
247 for (j = 0; j < width; j++)
262 replicate_rows (GdkPixbuf *src,
268 guint n_channels = gdk_pixbuf_get_n_channels (src);
269 guint src_rowstride = gdk_pixbuf_get_rowstride (src);
270 guchar *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x * n_channels);
273 guint dest_rowstride;
276 result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
281 g_warning ("failed to create a %dx%d pixbuf", width, height);
285 dest_rowstride = gdk_pixbuf_get_rowstride (result);
286 dest_pixels = gdk_pixbuf_get_pixels (result);
288 for (i = 0; i < height; i++)
289 memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
295 replicate_cols (GdkPixbuf *src,
301 guint n_channels = gdk_pixbuf_get_n_channels (src);
302 guint src_rowstride = gdk_pixbuf_get_rowstride (src);
303 guchar *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x * n_channels);
306 guint dest_rowstride;
309 result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
314 g_warning ("failed to create a %dx%d pixbuf", width, height);
318 dest_rowstride = gdk_pixbuf_get_rowstride (result);
319 dest_pixels = gdk_pixbuf_get_pixels (result);
321 for (i = 0; i < height; i++)
323 guchar *p = dest_pixels + dest_rowstride * i;
324 guchar *q = pixels + src_rowstride * i;
334 for (j = 0; j < width; j++)
348 /* Scale the rectangle (src_x, src_y, src_width, src_height)
349 * onto the rectangle (dest_x, dest_y, dest_width, dest_height)
350 * of the destination, clip by clip_rect and render
353 pixbuf_render (GdkPixbuf *src,
357 GdkRectangle *clip_rect,
367 GdkPixbuf *tmp_pixbuf = NULL;
369 int x_offset, y_offset;
370 gboolean has_alpha = gdk_pixbuf_get_has_alpha (src);
371 gint src_rowstride = gdk_pixbuf_get_rowstride (src);
372 gint src_n_channels = gdk_pixbuf_get_n_channels (src);
374 if (dest_width <= 0 || dest_height <= 0)
379 rect.width = dest_width;
380 rect.height = dest_height;
382 if (hints & THEME_MISSING)
385 /* FIXME: Because we use the mask to shape windows, we don't use
386 * clip_rect to clip what we draw to the mask, only to clip
387 * what we actually draw. But this leads to the horrible ineffiency
388 * of scale the whole image to get a little bit of it.
390 if (!mask && clip_rect)
392 if (!gdk_rectangle_intersect (clip_rect, &rect, &rect))
396 if (dest_width == src_width && dest_height == src_height)
398 tmp_pixbuf = g_object_ref (src);
400 x_offset = src_x + rect.x - dest_x;
401 y_offset = src_y + rect.y - dest_y;
403 else if (src_width == 0 && src_height == 0)
405 tmp_pixbuf = bilinear_gradient (src, src_x, src_y, dest_width, dest_height);
407 x_offset = rect.x - dest_x;
408 y_offset = rect.y - dest_y;
410 else if (src_width == 0 && dest_height == src_height)
412 tmp_pixbuf = horizontal_gradient (src, src_x, src_y, dest_width, dest_height);
414 x_offset = rect.x - dest_x;
415 y_offset = rect.y - dest_y;
417 else if (src_height == 0 && dest_width == src_width)
419 tmp_pixbuf = vertical_gradient (src, src_x, src_y, dest_width, dest_height);
421 x_offset = rect.x - dest_x;
422 y_offset = rect.y - dest_y;
424 else if ((hints & THEME_CONSTANT_COLS) && (hints & THEME_CONSTANT_ROWS))
426 tmp_pixbuf = replicate_single (src, src_x, src_y, dest_width, dest_height);
428 x_offset = rect.x - dest_x;
429 y_offset = rect.y - dest_y;
431 else if (dest_width == src_width && (hints & THEME_CONSTANT_COLS))
433 tmp_pixbuf = replicate_rows (src, src_x, src_y, dest_width, dest_height);
435 x_offset = rect.x - dest_x;
436 y_offset = rect.y - dest_y;
438 else if (dest_height == src_height && (hints & THEME_CONSTANT_ROWS))
440 tmp_pixbuf = replicate_cols (src, src_x, src_y, dest_width, dest_height);
442 x_offset = rect.x - dest_x;
443 y_offset = rect.y - dest_y;
445 else if (src_width > 0 && src_height > 0)
447 double x_scale = (double)dest_width / src_width;
448 double y_scale = (double)dest_height / src_height;
450 GdkPixbuf *partial_src;
452 pixels = (gdk_pixbuf_get_pixels (src)
453 + src_y * src_rowstride
454 + src_x * src_n_channels);
456 partial_src = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB,
458 8, src_width, src_height,
462 tmp_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
464 rect.width, rect.height);
466 gdk_pixbuf_scale (partial_src, tmp_pixbuf,
467 0, 0, rect.width, rect.height,
468 dest_x - rect.x, dest_y - rect.y,
470 GDK_INTERP_BILINEAR);
472 g_object_unref (partial_src);
482 gdk_pixbuf_render_threshold_alpha (tmp_pixbuf, mask,
485 rect.width, rect.height,
489 gdk_draw_pixbuf (window, NULL, tmp_pixbuf,
492 rect.width, rect.height,
493 GDK_RGB_DITHER_NORMAL,
495 g_object_unref (tmp_pixbuf);
500 theme_pixbuf_new (void)
502 ThemePixbuf *result = g_new0 (ThemePixbuf, 1);
503 result->filename = NULL;
504 result->pixbuf = NULL;
506 result->stretch = TRUE;
507 result->border_left = 0;
508 result->border_right = 0;
509 result->border_bottom = 0;
510 result->border_top = 0;
516 theme_pixbuf_destroy (ThemePixbuf *theme_pb)
518 theme_pixbuf_set_filename (theme_pb, NULL);
523 theme_pixbuf_set_filename (ThemePixbuf *theme_pb,
524 const char *filename)
526 if (theme_pb->pixbuf)
528 g_cache_remove (pixbuf_cache, theme_pb->pixbuf);
529 theme_pb->pixbuf = NULL;
532 g_free (theme_pb->filename);
535 theme_pb->filename = g_strdup (filename);
537 theme_pb->filename = NULL;
541 compute_hint (GdkPixbuf *pixbuf,
548 int hints = THEME_CONSTANT_ROWS | THEME_CONSTANT_COLS | THEME_MISSING;
549 int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
551 guchar *data = gdk_pixbuf_get_pixels (pixbuf);
552 int rowstride = gdk_pixbuf_get_rowstride (pixbuf);
554 if (x0 == x1 || y0 == y1)
557 for (i = y0; i < y1; i++)
559 guchar *p = data + i * rowstride + x0 * n_channels;
568 for (j = x0; j < x1 ; j++)
570 if (n_channels != 4 || p[3] != 0)
572 hints &= ~THEME_MISSING;
573 if (!(hints & THEME_CONSTANT_ROWS))
580 (n_channels != 4 && a != *(p++)))
582 hints &= ~THEME_CONSTANT_ROWS;
583 if (!(hints & THEME_MISSING))
590 for (i = y0 + 1; i < y1; i++)
592 guchar *base = data + y0 * rowstride + x0 * n_channels;
593 guchar *p = data + i * rowstride + x0 * n_channels;
595 if (memcmp (p, base, n_channels * (x1 - x0)) != 0)
597 hints &= ~THEME_CONSTANT_COLS;
606 theme_pixbuf_compute_hints (ThemePixbuf *theme_pb)
609 gint width = gdk_pixbuf_get_width (theme_pb->pixbuf);
610 gint height = gdk_pixbuf_get_height (theme_pb->pixbuf);
612 if (theme_pb->border_left + theme_pb->border_right > width ||
613 theme_pb->border_top + theme_pb->border_bottom > height)
615 g_warning ("Invalid borders specified for theme pixmap:\n"
617 "borders don't fit within the image", theme_pb->filename);
618 if (theme_pb->border_left + theme_pb->border_right > width)
620 theme_pb->border_left = width / 2;
621 theme_pb->border_right = (width + 1) / 2;
623 if (theme_pb->border_bottom + theme_pb->border_top > height)
625 theme_pb->border_top = height / 2;
626 theme_pb->border_bottom = (height + 1) / 2;
630 for (i = 0; i < 3; i++)
638 y1 = theme_pb->border_top;
641 y0 = theme_pb->border_top;
642 y1 = height - theme_pb->border_bottom;
645 y0 = height - theme_pb->border_bottom;
650 for (j = 0; j < 3; j++)
658 x1 = theme_pb->border_left;
661 x0 = theme_pb->border_left;
662 x1 = width - theme_pb->border_right;
665 x0 = width - theme_pb->border_right;
670 theme_pb->hints[i][j] = compute_hint (theme_pb->pixbuf, x0, x1, y0, y1);
677 theme_pixbuf_set_border (ThemePixbuf *theme_pb,
683 theme_pb->border_left = left;
684 theme_pb->border_right = right;
685 theme_pb->border_top = top;
686 theme_pb->border_bottom = bottom;
688 if (theme_pb->pixbuf)
689 theme_pixbuf_compute_hints (theme_pb);
693 theme_pixbuf_set_stretch (ThemePixbuf *theme_pb,
696 theme_pb->stretch = stretch;
698 if (theme_pb->pixbuf)
699 theme_pixbuf_compute_hints (theme_pb);
703 pixbuf_cache_value_new (gchar *filename)
707 GdkPixbuf *result = gdk_pixbuf_new_from_file (filename, &err);
710 g_warning ("Pixbuf theme: Cannot load pixmap file %s: %s\n",
711 filename, err->message);
719 theme_pixbuf_get_pixbuf (ThemePixbuf *theme_pb)
721 if (!theme_pb->pixbuf)
724 pixbuf_cache = g_cache_new ((GCacheNewFunc)pixbuf_cache_value_new,
725 (GCacheDestroyFunc)g_object_unref,
726 (GCacheDupFunc)g_strdup,
727 (GCacheDestroyFunc)g_free,
728 g_str_hash, g_direct_hash, g_str_equal);
730 theme_pb->pixbuf = g_cache_insert (pixbuf_cache, theme_pb->filename);
732 if (theme_pb->stretch)
733 theme_pixbuf_compute_hints (theme_pb);
736 return theme_pb->pixbuf;
740 theme_pixbuf_render (ThemePixbuf *theme_pb,
743 GdkRectangle *clip_rect,
744 guint component_mask,
751 GdkPixbuf *pixbuf = theme_pixbuf_get_pixbuf (theme_pb);
752 gint src_x[4], src_y[4], dest_x[4], dest_y[4];
753 gint pixbuf_width = gdk_pixbuf_get_width (pixbuf);
754 gint pixbuf_height = gdk_pixbuf_get_height (pixbuf);
759 if (theme_pb->stretch)
761 if (component_mask & COMPONENT_ALL)
762 component_mask = (COMPONENT_ALL - 1) & ~component_mask;
765 src_x[1] = theme_pb->border_left;
766 src_x[2] = pixbuf_width - theme_pb->border_right;
767 src_x[3] = pixbuf_width;
770 src_y[1] = theme_pb->border_top;
771 src_y[2] = pixbuf_height - theme_pb->border_bottom;
772 src_y[3] = pixbuf_height;
775 dest_x[1] = x + theme_pb->border_left;
776 dest_x[2] = x + width - theme_pb->border_right;
777 dest_x[3] = x + width;
779 if (dest_x[1] > dest_x[2])
781 component_mask &= ~(COMPONENT_NORTH | COMPONENT_SOUTH | COMPONENT_CENTER);
782 dest_x[1] = dest_x[2] = (dest_x[1] + dest_x[2]) / 2;
786 dest_y[1] = y + theme_pb->border_top;
787 dest_y[2] = y + height - theme_pb->border_bottom;
788 dest_y[3] = y + height;
790 if (dest_y[1] > dest_y[2])
792 component_mask &= ~(COMPONENT_EAST | COMPONENT_WEST | COMPONENT_CENTER);
793 dest_y[1] = dest_y[2] = (dest_y[1] + dest_y[2]) / 2;
798 #define RENDER_COMPONENT(X1,X2,Y1,Y2) \
799 pixbuf_render (pixbuf, theme_pb->hints[Y1][X1], window, mask, clip_rect, \
800 src_x[X1], src_y[Y1], \
801 src_x[X2] - src_x[X1], src_y[Y2] - src_y[Y1], \
802 dest_x[X1], dest_y[Y1], \
803 dest_x[X2] - dest_x[X1], dest_y[Y2] - dest_y[Y1]);
805 if (component_mask & COMPONENT_NORTH_WEST)
806 RENDER_COMPONENT (0, 1, 0, 1);
808 if (component_mask & COMPONENT_NORTH)
809 RENDER_COMPONENT (1, 2, 0, 1);
811 if (component_mask & COMPONENT_NORTH_EAST)
812 RENDER_COMPONENT (2, 3, 0, 1);
814 if (component_mask & COMPONENT_WEST)
815 RENDER_COMPONENT (0, 1, 1, 2);
817 if (component_mask & COMPONENT_CENTER)
818 RENDER_COMPONENT (1, 2, 1, 2);
820 if (component_mask & COMPONENT_EAST)
821 RENDER_COMPONENT (2, 3, 1, 2);
823 if (component_mask & COMPONENT_SOUTH_WEST)
824 RENDER_COMPONENT (0, 1, 2, 3);
826 if (component_mask & COMPONENT_SOUTH)
827 RENDER_COMPONENT (1, 2, 2, 3);
829 if (component_mask & COMPONENT_SOUTH_EAST)
830 RENDER_COMPONENT (2, 3, 2, 3);
836 x += (width - pixbuf_width) / 2;
837 y += (height - pixbuf_height) / 2;
839 pixbuf_render (pixbuf, 0, window, NULL, clip_rect,
841 pixbuf_width, pixbuf_height,
843 pixbuf_width, pixbuf_height);
847 cairo_t *cr = gdk_cairo_create (window);
849 gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
850 cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
853 gdk_cairo_rectangle (cr, clip_rect);
855 cairo_rectangle (cr, x, y, width, height);