From 139a02a6ef2476fbd39be0734fcc46c740d1145b Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 5 Feb 2008 19:52:35 +0000 Subject: [PATCH] Awesome progress git-svn-id: http://svn.nickg.me.uk/work/xcowsay@267 a97b1542-0b21-0410-a459-e47997c36f34 --- Makefile | 4 +- display_cow.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++ display_cow.h | 11 +++ floating_shape.c | 20 ++++- floating_shape.h | 5 ++ xcowsay.c | 82 ++++-------------- 6 files changed, 273 insertions(+), 70 deletions(-) create mode 100644 display_cow.c create mode 100644 display_cow.h diff --git a/Makefile b/Makefile index 286198a..f0a5096 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APPS:=xcowsay -OBJS:=xcowsay.o floating_shape.o -HEADERS:=floating_shape.h +OBJS:=xcowsay.o floating_shape.o display_cow.o +HEADERS:=floating_shape.h display_cow.h CFLAGS:=-Wall -g `pkg-config --cflags gtk+-2.0` LDFLAGS:=`pkg-config --libs gtk+-2.0` diff --git a/display_cow.c b/display_cow.c new file mode 100644 index 0000000..65c0f85 --- /dev/null +++ b/display_cow.c @@ -0,0 +1,221 @@ +#include +#include +#include + +#include +#include + +#include "floating_shape.h" +#include "display_cow.h" + +#define SPEED 10 // Horizontal speed in pixels per 100ms +#define LEFT_BUF 5 // Amount of pixels to leave after cow's tail +#define TIP_WIDTH 20 // Length of the triangle bit on the speech bubble +#define CORNER_RADIUS 30 // Radius of corners on the speech bubble +#define CORNER_DIAM CORNER_RADIUS*2 +#define BUBBLE_BORDER 5 // Pixels to leave free around edge of bubble +#define BUBBLE_XOFFSET 10 // Distance from cow to bubble +#define MIN_TIP_HEIGHT 15 + +typedef struct { + float_shape_t *cow, *bubble; + int bubble_width, bubble_height; + GdkPixbuf *cow_pixbuf, *bubble_pixbuf; +} xcowsay_t; + +static xcowsay_t xcowsay; + +static GdkPixbuf *load_cow() +{ + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file("cow_large.png", NULL); + if (NULL == pixbuf) { + fprintf(stderr, "Failed to load cow image!\n"); + exit(EXIT_FAILURE); + } + return pixbuf; +} + +static GdkPixbuf *create_bubble(char *text) +{ + GdkGC *bubble_gc; + GdkColor black, white, bright_green; + GdkColormap *colormap; + GdkPixmap *bubble_pixmap; + int bubble_width, bubble_height; + int text_width, text_height; + GdkPoint tip_points[5]; + + // Work out the size of the bubble from the text + PangoContext *pango_context = gdk_pango_context_get(); + PangoLayout *layout = pango_layout_new(pango_context); + PangoFontDescription *font = + pango_font_description_from_string("Bitstream Vera Sans 14"); + PangoAttrList *pango_attrs = NULL; + + char *stripped; + if (!pango_parse_markup(text, -1, 0, &pango_attrs, &stripped, NULL, NULL)) { + fprintf(stderr, "Warning: Failed to parse Pango attributes\n"); + stripped = text; + } + else { + pango_layout_set_attributes(layout, pango_attrs); + } + + pango_layout_set_font_description(layout, font); + pango_layout_set_text(layout, stripped, -1); + pango_layout_get_pixel_size(layout, &text_width, &text_height); + + bubble_width = 2*BUBBLE_BORDER + CORNER_DIAM + TIP_WIDTH + text_width; + bubble_height = BUBBLE_BORDER + CORNER_DIAM + text_height; + + bubble_pixmap = gdk_pixmap_new(shape_window(xcowsay.cow)->window, + bubble_width, bubble_height, -1); + bubble_gc = gdk_gc_new(bubble_pixmap); + + colormap = gdk_colormap_get_system(); + gdk_color_black(colormap, &black); + gdk_color_white(colormap, &white); + + bright_green.red = 0; + bright_green.green = 65535; // Bright green is alpha + bright_green.blue = 0; + gdk_gc_set_background(bubble_gc, &black); + gdk_gc_set_rgb_fg_color(bubble_gc, &bright_green); + + gdk_draw_rectangle(bubble_pixmap, bubble_gc, TRUE, 0, 0, + bubble_width, bubble_height); + + bubble_width -= BUBBLE_BORDER; + bubble_height -= BUBBLE_BORDER; + + // Draw the white corners + gdk_gc_set_foreground(bubble_gc, &white); + gdk_draw_arc(bubble_pixmap, bubble_gc, TRUE, TIP_WIDTH + BUBBLE_BORDER, + BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 90*64, 90*64); + gdk_draw_arc(bubble_pixmap, bubble_gc, TRUE, TIP_WIDTH + BUBBLE_BORDER, + bubble_height - CORNER_DIAM, CORNER_DIAM, + CORNER_DIAM, 180*64, 90*64); + gdk_draw_arc(bubble_pixmap, bubble_gc, TRUE, + bubble_width - CORNER_DIAM - BUBBLE_BORDER, + bubble_height - CORNER_DIAM, CORNER_DIAM, + CORNER_DIAM, 270*64, 90*64); + gdk_draw_arc(bubble_pixmap, bubble_gc, TRUE, + bubble_width - CORNER_DIAM - BUBBLE_BORDER, + BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 0*64, 90*64); + + // Fill in the middle of the bubble + gdk_draw_rectangle(bubble_pixmap, bubble_gc, TRUE, + CORNER_RADIUS + TIP_WIDTH + BUBBLE_BORDER, + BUBBLE_BORDER, + bubble_width - TIP_WIDTH - BUBBLE_BORDER - CORNER_DIAM, + bubble_height - BUBBLE_BORDER); + gdk_draw_rectangle(bubble_pixmap, bubble_gc, TRUE, + TIP_WIDTH + BUBBLE_BORDER, BUBBLE_BORDER + CORNER_RADIUS, + bubble_width - TIP_WIDTH - BUBBLE_BORDER*2, + bubble_height - BUBBLE_BORDER - CORNER_DIAM); + + // The points on the tip part + int tip_compute_offset = (bubble_height - BUBBLE_BORDER - CORNER_DIAM)/3; + int tip_offset[3] = { tip_compute_offset, tip_compute_offset, tip_compute_offset }; + if (tip_compute_offset < MIN_TIP_HEIGHT) { + int new_offset = (bubble_height - BUBBLE_BORDER - CORNER_DIAM - MIN_TIP_HEIGHT)/2; + tip_offset[0] = new_offset; + tip_offset[1] = MIN_TIP_HEIGHT; + tip_offset[2] = new_offset; + } + + tip_points[0].x = TIP_WIDTH + BUBBLE_BORDER; + tip_points[0].y = BUBBLE_BORDER + CORNER_RADIUS; + tip_points[1].x = TIP_WIDTH + BUBBLE_BORDER; + tip_points[1].y = BUBBLE_BORDER + CORNER_RADIUS + tip_offset[0]; + tip_points[2].x = BUBBLE_BORDER; + tip_points[2].y = BUBBLE_BORDER + CORNER_RADIUS + tip_offset[0] + tip_offset[1]/2; + tip_points[3].x = TIP_WIDTH + BUBBLE_BORDER; + tip_points[3].y = BUBBLE_BORDER + CORNER_RADIUS + tip_offset[0] + tip_offset[1]; + tip_points[4].x = TIP_WIDTH + BUBBLE_BORDER; + tip_points[4].y = bubble_height - CORNER_RADIUS; + + gdk_draw_polygon(bubble_pixmap, bubble_gc, TRUE, tip_points, 5); + + // Draw the black rounded corners + gdk_gc_set_line_attributes(bubble_gc, 4, GDK_LINE_SOLID, + GDK_CAP_ROUND, GDK_JOIN_ROUND); + gdk_gc_set_foreground(bubble_gc, &black); + gdk_draw_arc(bubble_pixmap, bubble_gc, FALSE, TIP_WIDTH + BUBBLE_BORDER, + BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 90*64, 90*64); + gdk_draw_arc(bubble_pixmap, bubble_gc, FALSE, TIP_WIDTH + BUBBLE_BORDER, + bubble_height - CORNER_DIAM, CORNER_DIAM, + CORNER_DIAM, 180*64, 90*64); + gdk_draw_arc(bubble_pixmap, bubble_gc, FALSE, + bubble_width - CORNER_DIAM - BUBBLE_BORDER, + bubble_height - CORNER_DIAM, CORNER_DIAM, + CORNER_DIAM, 270*64, 90*64); + gdk_draw_arc(bubble_pixmap, bubble_gc, FALSE, + bubble_width - CORNER_DIAM - BUBBLE_BORDER, + BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 0*64, 90*64); + + gdk_draw_lines(bubble_pixmap, bubble_gc, tip_points, 5); + + // Draw the top, bottom, and right sides (easy as they're straight!) + gdk_draw_line(bubble_pixmap, bubble_gc, + bubble_width - BUBBLE_BORDER, + CORNER_RADIUS + BUBBLE_BORDER, + bubble_width - BUBBLE_BORDER, bubble_height - CORNER_RADIUS); + gdk_draw_line(bubble_pixmap, bubble_gc, + BUBBLE_BORDER + TIP_WIDTH + CORNER_RADIUS, BUBBLE_BORDER, + bubble_width - CORNER_RADIUS, BUBBLE_BORDER); + gdk_draw_line(bubble_pixmap, bubble_gc, + BUBBLE_BORDER + TIP_WIDTH + CORNER_RADIUS, bubble_height, + bubble_width - CORNER_RADIUS, bubble_height); + + xcowsay.bubble_width = bubble_width + BUBBLE_BORDER; + xcowsay.bubble_height = bubble_height + BUBBLE_BORDER; + + // Render the text + gdk_draw_layout(bubble_pixmap, bubble_gc, + BUBBLE_BORDER + TIP_WIDTH + CORNER_RADIUS, + CORNER_RADIUS, layout); + + // Make sure to free the Pango objects + g_object_unref(pango_context); + g_object_unref(layout); + pango_font_description_free(font); + if (NULL != pango_attrs) + pango_attr_list_unref(pango_attrs); + + GdkPixbuf *pixbuf = + gdk_pixbuf_get_from_drawable(NULL, bubble_pixmap, NULL, + 0, 0, 0, 0, + bubble_width + BUBBLE_BORDER, + bubble_height + BUBBLE_BORDER); + g_object_unref(bubble_pixmap); + return pixbuf; +} + +void cowsay_init(void) +{ + xcowsay.cow = NULL; + xcowsay.bubble = NULL; + xcowsay.bubble_pixbuf = NULL; + xcowsay.cow_pixbuf = load_cow(); +} + +void display_cow(char *text) +{ + g_assert(xcowsay.cow_pixbuf); + xcowsay.cow = make_shape_from_pixbuf(xcowsay.cow_pixbuf); + show_shape(xcowsay.cow); + + xcowsay.bubble_pixbuf = create_bubble(text); + xcowsay.bubble = make_shape_from_pixbuf(xcowsay.bubble_pixbuf); + int bx = shape_x(xcowsay.cow) + shape_width(xcowsay.cow) + BUBBLE_XOFFSET; + int by = shape_y(xcowsay.cow) + + (shape_width(xcowsay.cow) - shape_width(xcowsay.bubble))/2; + move_shape(xcowsay.bubble, bx, by); + + show_shape(xcowsay.bubble); + + gtk_main(); + + printf("Here!\n"); +} diff --git a/display_cow.h b/display_cow.h new file mode 100644 index 0000000..482b927 --- /dev/null +++ b/display_cow.h @@ -0,0 +1,11 @@ +#ifndef INC_DISPLAY_COW_H +#define INC_DISPLAY_COW_H + +#include +#include + +// Show a cow with the given string and clean up afterwards +void display_cow(char *text); +void cowsay_init(void); + +#endif diff --git a/floating_shape.c b/floating_shape.c index 4ae0574..046db51 100644 --- a/floating_shape.c +++ b/floating_shape.c @@ -52,10 +52,14 @@ static void get_alpha_mask(float_shape_t *shape) for (y = 0; y < shape->height; y++) { for (x = 0; x < shape->width; x++) { p = pixels + y*rowstride + x*nchannels; - bright_green = 0 != p[0] && 255 != p[1] && 0 != p[2]; - if ((has_alpha && 255 == p[3]) // p[3] is alpha channel - || bright_green) // Green is also alpha + bright_green = 0 == p[0] && 255 == p[1] && 0 == p[2]; + if (has_alpha) { + if (255 == p[3]) // p[3] is alpha channel + gdk_draw_point(shape->mask_bitmap, gc, x, y); + } + else if (!bright_green) { // Bright green is alpha for RGB images gdk_draw_point(shape->mask_bitmap, gc, x, y); + } } } } @@ -73,12 +77,13 @@ float_shape_t *make_shape_from_pixbuf(GdkPixbuf *pixbuf) gtk_window_set_decorated(GTK_WINDOW(s->window), FALSE); gtk_window_set_title(GTK_WINDOW(s->window), "shape"); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(s->window), TRUE); + gtk_window_set_keep_above(GTK_WINDOW(s->window), TRUE); s->image = gtk_image_new_from_pixbuf(pixbuf); gtk_container_add(GTK_CONTAINER(s->window), s->image); get_alpha_mask(s); - //gtk_widget_shape_combine_mask(s->window, s->mask_bitmap, 0, 0); + gtk_widget_shape_combine_mask(s->window, s->mask_bitmap, 0, 0); g_signal_connect(G_OBJECT(s->window), "destroy", G_CALLBACK(quit_callback), NULL); @@ -99,3 +104,10 @@ void move_shape(float_shape_t *shape, int x, int y) shape->y = y; gtk_window_move(GTK_WINDOW(shape->window), shape->x, shape->y); } + +void free_shape(float_shape_t *shape) +{ + g_assert(shape); + + free(shape); +} diff --git a/floating_shape.h b/floating_shape.h index 7af1313..ed0ced6 100644 --- a/floating_shape.h +++ b/floating_shape.h @@ -18,7 +18,12 @@ typedef struct { float_shape_t *make_shape_from_pixbuf(GdkPixbuf *pixbuf); void move_shape(float_shape_t *shape, int x, int y); void show_shape(float_shape_t *shape); +void free_shape(float_shape_t *shape); #define shape_window(s) (s->window) +#define shape_x(s) (s->x) +#define shape_y(s) (s->y) +#define shape_width(s) (s->width) +#define shape_height(s) (s->height) #endif diff --git a/xcowsay.c b/xcowsay.c index 7cce8bb..deaf4fe 100644 --- a/xcowsay.c +++ b/xcowsay.c @@ -1,79 +1,33 @@ #include #include -#include -#include -#include +#include "display_cow.h" -#include "floating_shape.h" +#define MAX_LINE 1024 // Max chars to read in one go -#define SPEED 10 // Horizontal speed in pixels per 100ms -#define LEFT_BUF 5 // Amount of pixels to leave after cow's tail - -typedef struct { - float_shape_t *cow, *bubble; -} xcowsay_t; - -static xcowsay_t xcowsay; - -static GdkPixbuf *load_cow() +static void read_from_stdin(void) { - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file("cow_large.png", NULL); - if (NULL == pixbuf) { - fprintf(stderr, "Failed to load cow image!\n"); - exit(EXIT_FAILURE); - } - return pixbuf; -} - -static GdkPixbuf *create_bubble() -{ - GdkGC *bubble_gc; - GdkColor black, white, bright_green; - GdkColormap *colormap; - GdkPixmap *bubble_pixmap; - int bubble_width, bubble_height; - - bubble_width = 200; - bubble_height = 100; - - bubble_pixmap = gdk_pixmap_new(shape_window(xcowsay.cow)->window, - bubble_width, bubble_height, -1); - bubble_gc = gdk_gc_new(bubble_pixmap); - - colormap = gdk_colormap_get_system(); - gdk_color_black(colormap, &black); - gdk_color_white(colormap, &white); - - bright_green.red = 0; - bright_green.green = 65535; // Bright green is alpha - bright_green.blue = 0; - gdk_gc_set_background(bubble_gc, &black); - gdk_gc_set_rgb_fg_color(bubble_gc, &bright_green); - - gdk_draw_rectangle(bubble_pixmap, bubble_gc, TRUE, 0, 0, - bubble_width, bubble_height); - - gdk_gc_set_foreground(bubble_gc, &white); - gdk_draw_arc(bubble_pixmap, bubble_gc, TRUE, 0, 0, - bubble_width, bubble_height, 0, 360*64); - - return gdk_pixbuf_get_from_drawable(NULL, bubble_pixmap, NULL, - 0, 0, 0, 0, bubble_width, - bubble_height); + /*char line[MAX_LINE]; + while (EOF != fgets(line, MAX_LINE, stdin)) { + display_cow(line); + }*/ } int main(int argc, char **argv) -{ +{ gtk_init(&argc, &argv); - xcowsay.cow = make_shape_from_pixbuf(load_cow()); - show_shape(xcowsay.cow); - - xcowsay.bubble = make_shape_from_pixbuf(create_bubble()); - show_shape(xcowsay.bubble); + cowsay_init(); - gtk_main(); + if (argc == 1) { + read_from_stdin(); + } + else if (argc == 2) { + display_cow(argv[1]); + } + else { + fprintf(stderr, "Error: Too many arguments\n"); + } return EXIT_SUCCESS; } -- 2.39.2