/* The GIMP -- an image manipulation program * * Multitile plug-in ---- generates different symmetric patterns * Copyright (C) 2000 Praveen Mishra * pmishra@mum.edu * Copyright (C) 2000 Gurdy Leete * gleete@mum.edu * Copyright (C) 2000 Chen Yu * * 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. * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This plug-in makes symmetric patterns from a selection of any shape * and tiles the result to the rest of the layer. This was inspired by * the commercial Photoshop plug-in Terrazzo, and should be useful to * artists who like symmetric patterns. */ /* ToDo List * More symmetry types * Real time update while you move the selection in the original preview * Feather functions for other symmetry */ #include #include #include #include #include "gtk/gtk.h" #include "libgimp/gimp.h" /* Magic Numbers */ #define ENTRY_WIDTH 60 #define ENTRY_HEIGHT 25 #define SCALE_WIDTH 80 #define PREVIEW_SIZE 128 #define SQ_SIZE 8 #ifndef TRUE #define TRUE 1 #endif /* TRUE */ #ifndef FALSE #define FALSE 0 #endif /* FALSE */ #define PREVIEW_MASK GDK_EXPOSURE_MASK | \ GDK_MOTION_NOTIFY | \ GDK_POINTER_MOTION_MASK | \ GDK_BUTTON_PRESS_MASK | \ GDK_BUTTON_RELEASE_MASK | \ GDK_BUTTON_MOTION_MASK /* Even more stuff from Quartics plugins */ #define CHECK_SIZE 8 #define CHECK_DARK ((int) (1.0 / 3.0 * 255)) #define CHECK_LIGHT ((int) (2.0 / 3.0 * 255)) /* type define */ typedef enum SobjType { SQUARE, RECTANGLE, TRIANGLE } SOBJTYPE; typedef struct _runit { gint32 image; gint32 drawable; GtkWidget *dialog; }RunIt; RunIt runit; struct Sobject; /* fwd declaration for SOBJFUNC */ typedef void (*SOBJFUNC) (void ); typedef gint (*SOBJINSIDEFUNC) (GdkPoint pnt, GdkPoint *poly); typedef void (*SOBJMOVEFUNC) (GdkPoint *to_pnt); typedef void (*SOBJRESIZEFUNC) (GdkPoint *to_pnt); /* The selection object */ typedef struct Sobject { SOBJTYPE type; /* What is the type? */ GdkPoint points[2]; /* List of points */ SOBJFUNC draw_func; /* How do I draw myself */ SOBJINSIDEFUNC inside_func; /* Inside selection area? */ SOBJMOVEFUNC move_func; /* How do I move myself */ SOBJRESIZEFUNC resize_func; /* How do I resize myself */ } SOBJECT; typedef enum SymmetryType { GOLDBRICK, CRABCLAWS, HONEYBEE, PRICKLYPEAR, PINWHEEL, SUNFLOWER } SYMMETRYTYPE; typedef enum OperType { NULL_OPER, MOVE, RESIZE, } OPERTYPE; typedef struct { GtkWidget *bef_preview; GtkWidget *aft_preview; gint bef_preview_exp_id; guchar preview_row[PREVIEW_SIZE * 4]; guchar *bef_pv_cache; guchar *aft_pv_cache; GdkGC *gui_gc; GtkWidget *opacity_entry; GtkWidget *feather_entry; GtkWidget *feather_slider; gint img_bpp; OPERTYPE otype ; GdkPoint move_start_point; SOBJECT selection; SYMMETRYTYPE symmetry_type; gint run; } Sel_Interface; /* Declare a local function.*/ static void query (void); static void run (char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals); static void pinwheel_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny); static void goldbrick_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny); static void crabclaws_tiles_xy ( gint width, gint height, gint x, gint y, gint *nx, gint *ny); static void pricklypear_tiles_xy(gint width, gint height, gint x, gint y, gint *nx, gint *ny); static void sunflower_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny); static void multitile (gint32 image_id, gint32 drawable_id); static void tile (gint32 center_x, gint32 center_y, GimpDrawable *src_drawable, GimpDrawable *dest_drawable); static void merge_region (GimpPixelRgn *front_rgn, GimpPixelRgn *back_rgn, GimpPixelRgn *dest_rgn); static void make_org_tile (GimpDrawable *src_drawable, gint sel_x1, gint sel_y1, GimpDrawable *dest_drawable, gint dx, gint dy); static void make_prv_fea_cache (guchar *src_cache, guchar *dest_cache, gint src_width, gint x1, gint y1, gint width, gint height, gint dx, gint dy); static void merge_preview_cache_region (gint x1, gint y1, gint t_x1, gint t_y1, gint width, gint height, gint t_width, gint t_height, guchar *tile_cache); static gint main_dialog ( ); static void tile_close_callback (GtkWidget *widget, gpointer data); static void tile_ok_callback (GtkWidget *widget, gpointer data); static void tile_cancel_callback (GtkWidget *widget, gpointer data); static void symmetry_menu_callback (GtkWidget *widget, gpointer data); static void multitile_entry_update (GtkWidget *widget, gpointer data); static void multitile_scale_update (GtkAdjustment *adjustment, gpointer data); static gint bef_preview_expose (GtkWidget *widget,GdkEvent *event ); static gint bef_preview_events (GtkWidget *widget,GdkEvent *event ); static void do_preview (guchar *dest_row, guchar *src_row, gint width, gint dh, gint height, gint bpp); static void new_gc (void); static void create_selection (void ); static void draw_control_square (void); static void draw_rectangle (void); static void draw_triangle (void); static void move_sel_obj (GdkPoint *to_pnt); static void update_pnts (gint16 xdiff, gint16 ydiff); static void resize_square (GdkPoint *to_pnt); static void resize_rectangle (GdkPoint *to_pnt); static void resize_triangle (GdkPoint *to_pnt); static gint inside_rectangle (GdkPoint pnt, GdkPoint *rect); static gint inside_triangle (GdkPoint pnt, GdkPoint *tri); static gint inside_control_square (GdkPoint pnt); static void dialog_update_preview (void); static void update_preview_cache (void); static void cache_preview (void); static void update_draw_area (GtkWidget *widget,GdkEvent *event); static void get_diffs (gint16 *xdiff, gint16 *ydiff, GdkPoint *to_pnt, GdkPoint *from_pnt); static gdouble pnts_dist (gint x1, gint y1, gint x2, gint y2); static void multiply_alpha (guchar *buf, gint width, gint bytes); /* variables */ GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; static Sel_Interface tint = { NULL, /* bef_preview */ NULL, /* aft_preview */ 0, /* bef_preview_exp_id */ {'4','u'},/* Preview_row */ NULL, /* before_preview cache */ NULL, /* after_preview cache */ NULL, /* gui_gc */ NULL, /* opacity_entry */ NULL, /* feather_entry */ NULL, /* feather_slider */ 4, /* img_bpp */ NULL_OPER,/* otype */ {-1,-1}, /* move_start_point*/ {RECTANGLE}, /* selection*/ HONEYBEE, /* symmetry_type */ FALSE, /* run */ }; /* data for selection */ static gint sel_x1, sel_y1, sel_x2, sel_y2; static gint sel_width, sel_height; /* data for preview */ static gint preview_width, preview_height; /* date for oringinal image */ static gint img_width, img_height,img_bpp; static gint has_alpha = 0; static GimpDrawable *drawable; static gdouble scale_x_factor,scale_y_factor; /* opacity value */ static gint opacity = 100; /* feather value */ static gint feather = 0; /* data for system tile */ static gint tile_width, tile_height; static GimpTile *the_tile = NULL; /* Action type on cell */ #define HORIZONTAL 0x1 #define VERTICAL 0x2 gint sunflower_tileactions[2][2]= { {0, HORIZONTAL}, {VERTICAL, HORIZONTAL | VERTICAL} }; gint pricklypear_tileactions[2][2]= { {0, HORIZONTAL}, {VERTICAL, HORIZONTAL | VERTICAL} }; gint honeybee_tileactions[2][2]= { {0, VERTICAL}, {HORIZONTAL, HORIZONTAL | VERTICAL} }; gint pinwheel_tileactions[2][2]= { {0, 270}, {90, 180} }; gint goldbrick_tileactions[2][2]= { {0, 0}, {0, 0} }; gint crabclaws_tileactions[2][2]= { {0,0}, {180, 0} }; /* variables for progress */ static gint progress, max_progress; GtkTooltips *tooltips; MAIN () static void query () { static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, }; static GimpParamDef *return_vals = NULL; static int nargs = sizeof (args) / sizeof (args[0]); static int nreturn_vals = 0; gimp_install_procedure ("plug_in_multitile", "Imitate photoshop's filter Terrazzo", "Imitate photoshop's filter Terrazzo", "Praveen, Mishra ", "Chen, Yu", "1999", "/Filters/Map/Multitile", "RGB*, GRAY*", GIMP_PLUGIN, nargs, nreturn_vals, args, return_vals); } static void run (char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; GimpRunModeType run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gint pwidth, pheight; gint32 image; run_mode = param[0].data.d_int32; *nreturn_vals = 1; *return_vals = values; drawable = gimp_drawable_get (param[2].data.d_drawable); image = gimp_drawable_image ((gint32) drawable); img_width = gimp_drawable_width (drawable->id); img_height = gimp_drawable_height (drawable->id); img_bpp = gimp_drawable_bpp (drawable->id); has_alpha = gimp_drawable_has_alpha (drawable->id); tile_width = gimp_tile_width(); tile_height = gimp_tile_height(); gimp_drawable_mask_bounds(drawable->id, &sel_x1, &sel_y1, &sel_x2, &sel_y2); sel_width = sel_x2 - sel_x1; sel_height = sel_y2 - sel_y1; /* Calculate preview size */ if (sel_width > sel_height) { pwidth = MIN(sel_width, PREVIEW_SIZE); pheight = sel_height * pwidth / sel_width; } else { pheight = MIN(sel_height, PREVIEW_SIZE); pwidth = sel_width * pheight / sel_height; } preview_width = MAX(pwidth, 2); /* Min size is 2 */ preview_height = MAX(pheight, 2); scale_x_factor = (gdouble)sel_width/(gdouble)preview_width; scale_y_factor = (gdouble)sel_height/(gdouble)preview_height; switch (run_mode) { case GIMP_RUN_INTERACTIVE: /* First acquire information with a dialog */ if (! main_dialog (image, drawable)) return; break; case GIMP_RUN_NONINTERACTIVE: /* Make sure all the arguments are there! */ if (nparams != 3) status = GIMP_PDB_CALLING_ERROR; break; case GIMP_RUN_WITH_LAST_VALS: break; default: break; } if (!gimp_drawable_is_gray (drawable->id) && !gimp_drawable_is_gray (drawable->id)) status = GIMP_PDB_EXECUTION_ERROR; if (status == GIMP_PDB_SUCCESS) { multitile(param[1].data.d_image, drawable->id); if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush (); } gimp_drawable_detach (drawable); values[0].data.d_status = status; } /*****/ static void tile_get_pixel (gint x, gint y, GimpDrawable *drawable, guchar *pixel) { static gint row = -1; static gint col = -1; gint newcol, newrow; gint newcoloff, newrowoff; guchar *p; int i; if ((x < 0) || (x >= drawable->width) || (y < 0) || (y >= drawable->height)) { pixel[0] = 0; pixel[1] = 0; pixel[2] = 0; pixel[3] = 0; return; } newcol = x / tile_width; newcoloff = x % tile_width; newrow = y / tile_height; newrowoff = y % tile_height; if ((col != newcol) || (row != newrow) || (the_tile == NULL)) { if (the_tile != NULL) gimp_tile_unref(the_tile, FALSE); the_tile = gimp_drawable_get_tile(drawable, FALSE, newrow, newcol); gimp_tile_ref(the_tile); col = newcol; row = newrow; } p = the_tile->data + the_tile->bpp * (the_tile->ewidth * newrowoff + newcoloff); for (i = drawable->bpp; i; i--) *pixel++ = *p++; }/* end of tile_get_pixel */ /******/ static void honeybee_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny) { gint col_off,row_off; gint col_num,row_num; gint actiontype; row_num = y / height; col_num = x / width; row_off = y % height; col_off = x % width; if((actiontype = honeybee_tileactions[col_num][row_num])) { if(actiontype & HORIZONTAL) row_off = height - row_off - 1; if(actiontype & VERTICAL) col_off = width - col_off - 1; } *nx = col_off; *ny = row_off; }/* end of honeybee_tiles */ /* Get the xy pos */ static void pinwheel_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny) { gint col_off,row_off; gint org_col_off,org_row_off; gint col_num,row_num; gint actiontype; row_num = y / height; col_num = x / width; org_row_off = row_off = y % height; org_col_off = col_off = x % width; actiontype = pinwheel_tileactions[col_num][row_num]; switch (actiontype ) { case 0: break; case 90: row_off = width - org_col_off - 1; col_off = org_row_off; break; case 180: row_off = height - org_row_off - 1; col_off = width - org_col_off - 1; break; case 270: row_off = org_col_off; col_off = height - org_row_off -1; break; default: printf ("Error action!\n"); break; } *nx = col_off; *ny = row_off; } /*******/ static void goldbrick_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny) { gint col_off,row_off; gint org_col_off,org_row_off; gint col_num,row_num; gint actiontype; row_num = y / height; col_num = x / width; org_row_off = row_off = y % height; org_col_off = col_off = x % width; actiontype = goldbrick_tileactions[col_num][row_num]; *nx = col_off; *ny = row_off; }/* end of goldbrick */ /*******/ static void crabclaws_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny) { gint col_off,row_off; gint org_col_off,org_row_off; gint col_num,row_num; gint actiontype; row_num = y / height; col_num = x / width; org_row_off = row_off = y % height; org_col_off = col_off = x % width; actiontype = crabclaws_tileactions[col_num][row_num]; switch (actiontype ) { case 0: break; case 90: row_off = width - org_col_off - 1; col_off = org_row_off; break; case 180: row_off = height - org_row_off - 1; col_off = width - org_col_off - 1; break; case 270: row_off = org_col_off; col_off = height - org_row_off -1; break; default: printf ("Error action!\n"); break; } *nx = col_off; *ny = row_off; }/* end of crabclaws */ static void sunflower_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny) { gint col_off,row_off; gint col_num,row_num; gint actiontype; gint temp; row_num = y / height; col_num = x / width; row_off = y % height; col_off = x % width; if((actiontype = sunflower_tileactions[col_num][row_num])) { if(actiontype & HORIZONTAL) row_off = height - row_off - 1; if(actiontype & VERTICAL) col_off = width - col_off - 1; } if (row_off < col_off) { temp = row_off; row_off = col_off; col_off = temp; } *nx = col_off; *ny = row_off; } static void pricklypear_tiles_xy (gint width, gint height, gint x, gint y, gint *nx, gint *ny) { gint col_off,row_off; gint col_num,row_num; gint actiontype; row_num = y / height; col_num = x / width; row_off = y % height; col_off = x % width; if((actiontype = pricklypear_tileactions[col_num][row_num])) { if(actiontype & HORIZONTAL) row_off = height - row_off - 1; if(actiontype & VERTICAL) col_off = width - col_off - 1; } *nx = col_off; *ny = row_off; } static void make_org_tile ( GimpDrawable *src_drawable, gint x1, gint y1, GimpDrawable *dest_drawable, gint dx, gint dy) { GimpPixelRgn src_rgn, dest_rgn, back_rgn; gint fea_rgn_width, fea_rgn_height; gint sel_rgn_width, sel_rgn_height; gint nx[2], ny[2]; gint num, j, k; gint alpha; gpointer pr; guchar *back_row, *dest_row; guchar *back, *dest; guchar pixel[8]; gint row, col; gint cen_x, cen_y; gdouble radius, d; gdouble dist[2], back_opa, dist_sum; gdouble temp; sel_rgn_width = dest_drawable->width; sel_rgn_height = dest_drawable->height; fea_rgn_width = sel_rgn_width + 2 * dx; fea_rgn_height = sel_rgn_height + 2 * dy; cen_x = sel_rgn_width / 2; cen_y = sel_rgn_height / 2; radius = pnts_dist (0, 0, cen_x, cen_y); gimp_pixel_rgn_init (&src_rgn, src_drawable, x1 - dx, y1 - dy, fea_rgn_width, fea_rgn_height, FALSE, FALSE); gimp_pixel_rgn_init (&back_rgn, src_drawable, x1, y1, sel_rgn_width, sel_rgn_height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, dest_drawable, 0, 0, sel_rgn_width, sel_rgn_height, TRUE, TRUE); for (pr = gimp_pixel_rgns_register (2, &dest_rgn, &back_rgn); pr != NULL; pr = gimp_pixel_rgns_process (pr)) { dest_row = dest_rgn.data; back_row = back_rgn.data; for (row = dest_rgn.y; row < (dest_rgn.y + dest_rgn.h); row++) { dest = dest_row; back = back_row; for (col = dest_rgn.x; col < (dest_rgn.x + dest_rgn.w); col++) { num = 0; d = pnts_dist (col, row, cen_x, cen_y); back_opa = 1 - d / radius; switch (tint.symmetry_type) { case HONEYBEE: if (col < cen_x) { nx[num] = col + sel_rgn_width; ny[num] = sel_rgn_height - row ; num++; } else { nx[num] = col - sel_rgn_width; ny[num] = sel_rgn_height - row; num++; } if (row < cen_y) { nx[num] = sel_rgn_width - col; ny[num] = row + sel_rgn_height; num++; } else { nx[num] = sel_rgn_width - col; ny[num] = row - sel_rgn_height; num++; } break; default: break; } dist_sum = 0; for (j = 0; j < num; j++) { if (nx[j] < -dx) nx[j] = -dx; else if (nx[j] >= sel_rgn_width + dx) nx[j] = sel_rgn_width + dx - 1; if (ny[j] < -dy) ny[j] = -dy; else if (ny[j] >= sel_rgn_height + dy) nx[j] = sel_rgn_height + dy - 1; gimp_pixel_rgn_get_pixel (&src_rgn, pixel + j * dest_rgn.bpp, x1 + nx[j], y1 + ny[j]); dist[j] = pnts_dist (nx[j], ny[j],cen_x, cen_y); dist_sum += dist[j]; } if (has_alpha) { alpha = back[dest_rgn.bpp - 1]; multiply_alpha (pixel, num, dest_rgn.bpp); for (k = 0; k < dest_rgn.bpp - 1; k++) { temp = 0; for (j = 0 ; j < num; j++) { temp += (1 - back_opa) * pixel[j * dest_rgn.bpp + k] * (1 - dist[j] / dist_sum); } temp += back[k] * back_opa * alpha / 255; dest[k] = (gint) (temp * 255 / alpha); dest[k] = MIN (255, dest[k]); } dest[dest_rgn.bpp - 1] = alpha; } else { for (k = 0; k < dest_rgn.bpp; k++) { temp = 0; for (j = 0 ; j < num; j++) { temp += (1 - back_opa) * pixel[j * dest_rgn.bpp + k] * (1 - dist[j] / dist_sum); } temp += back[k] * back_opa; dest[k] = (gint) (temp); dest[k] = MIN (255, dest[k]); } } dest += dest_rgn.bpp; back += back_rgn.bpp; }/* for col */ back_row += back_rgn.rowstride; dest_row += dest_rgn.rowstride; } /* for row */ progress += dest_rgn.w * dest_rgn.h; gimp_progress_update ((double) progress / (double) max_progress); } gimp_drawable_flush(dest_drawable); gimp_drawable_merge_shadow (dest_drawable->id, TRUE); } static void merge_preview_cache_region (gint x1, gint y1, gint t_x1, gint t_y1, gint width, gint height, gint t_width, gint t_height, guchar *tile_cache) { gint k; guchar *front, *back, *dest; gint row, col; for (row = 0; row < height; row++) { for (col = 0; col < width; col++) { dest = &(tint.aft_pv_cache[((row+y1) * preview_width + (col+x1)) * tint.img_bpp]); back = &(tint.bef_pv_cache[((row+y1) * preview_width + (col+x1)) * tint.img_bpp]); front = &(tile_cache[((row+t_y1) * t_width + (col+t_x1)) * tint.img_bpp]); for(k = 0; k < tint.img_bpp; k++) dest[k] = (front[k] * opacity + back[k] * (100 - opacity)) / 100; if (has_alpha) dest[tint.img_bpp - 1] = back[tint.img_bpp - 1]; } /* col */ } /* row */ } static void merge_region (GimpPixelRgn *front_rgn_pr, GimpPixelRgn *back_rgn_pr, GimpPixelRgn *dest_rgn_pr) { gint k; gpointer pr; guchar *front_row, *back_row, *dest_row; guchar *front, *back, *dest; gint row, col; gint alpha_f, alpha_b; gint bpp; bpp = back_rgn_pr->bpp; for (pr = gimp_pixel_rgns_register (3, front_rgn_pr, back_rgn_pr, dest_rgn_pr); pr != NULL; pr = gimp_pixel_rgns_process (pr)) { front_row = front_rgn_pr->data; back_row = back_rgn_pr->data; dest_row = dest_rgn_pr->data; for (row = dest_rgn_pr->y; row < (dest_rgn_pr->y + dest_rgn_pr->h); row++) { front = front_row; back = back_row; dest = dest_row; for (col = dest_rgn_pr->x; col < (dest_rgn_pr->x + dest_rgn_pr->w); col++) { if (has_alpha) { alpha_b = back[bpp - 1]; alpha_f = front[bpp - 1]; for (k = 0; k < bpp - 1; k++) dest[k] = (front[k] * alpha_f * opacity + back[k] * alpha_b * (100 - opacity)) / (alpha_b * 100); dest[bpp - 1] = alpha_b; } else { for (k = 0; k < bpp; k++) dest[k] = (front[k] * opacity + back[k] * (100 - opacity)) / 100; } dest += bpp; back += bpp; front += bpp; }/* for col */ front_row += front_rgn_pr->rowstride; back_row += back_rgn_pr->rowstride; dest_row += dest_rgn_pr->rowstride; } /* for row */ progress += dest_rgn_pr->w * dest_rgn_pr->h; gimp_progress_update ((double) progress / (double) max_progress); } } static void tile (gint32 center_x, gint32 center_y, GimpDrawable *src_drawable, GimpDrawable *dest_drawable) { gint old_width, old_height; gint width, height; gint sel_width, sel_height; gint sel_x1, sel_y1,sel_x2, sel_y2 ; gint i, j; GimpPixelRgn front_rgn, dest_rgn, back_rgn; gimp_drawable_mask_bounds(dest_drawable->id, &sel_x1, &sel_y1, &sel_x2, &sel_y2); sel_width = sel_x2 - sel_x1; sel_height = sel_y2 - sel_y1; old_width = src_drawable->width; old_height = src_drawable->height; /* tile the top left part... */ for (i = center_y; i >= 0; i -= old_height) { height = old_height; if (i - height < 0) height = i; for (j = center_x; j >= 0; j -= old_width) { width = old_width; if (j - width < 0) width = j; gimp_pixel_rgn_init (&front_rgn, src_drawable, old_width-width, old_height-height, width, height, FALSE, TRUE); gimp_pixel_rgn_init (&back_rgn, dest_drawable, sel_x1+j-width, sel_y1+i-height, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, dest_drawable, sel_x1+j-width, sel_y1+i-height, width, height, TRUE, TRUE); merge_region (&front_rgn, &back_rgn, &dest_rgn); } } /* tile the top right part... */ for (i = center_y; i >= 0; i -= old_height) { height = old_height; if (i - height < 0) height = i; for (j = center_x; j < sel_width; j += old_width) { width = old_width; if (j + width > sel_width) width = sel_width - j; gimp_pixel_rgn_init (&front_rgn, src_drawable, 0, old_height-height, width, height, FALSE, TRUE); gimp_pixel_rgn_init (&back_rgn, dest_drawable, sel_x1+j, sel_y1+i-height, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, dest_drawable, sel_x1+j, sel_y1+i-height, width, height, TRUE, TRUE); merge_region (&front_rgn, &back_rgn, &dest_rgn); } } /* tile the bottom left part... */ for (i = center_y; i < sel_height; i += old_height) { height = old_height; if (i + height > sel_height) height = sel_height - i; for (j = center_x; j >= 0; j -= old_width) { width = old_width; if (j - width < 0) width = j; gimp_pixel_rgn_init (&front_rgn, src_drawable, old_width-width, 0, width, height, FALSE, TRUE); gimp_pixel_rgn_init (&back_rgn, dest_drawable, sel_x1+j-width, sel_y1+i, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, dest_drawable, sel_x1+j-width, sel_y1+i, width, height, TRUE, TRUE); merge_region (&front_rgn, &back_rgn, &dest_rgn); } } /* tile the bottom right part... */ for (i = center_y; i < sel_height; i += old_height) { height = old_height; if (height + i > sel_height) height = sel_height - i; for (j = center_x; j < sel_width; j += old_width) { width = old_width; if (width + j > sel_width) width = sel_width - j; gimp_pixel_rgn_init (&front_rgn, src_drawable, 0, 0, width, height, FALSE, TRUE); gimp_pixel_rgn_init (&back_rgn, dest_drawable, sel_x1+j, sel_y1+i, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, dest_drawable, sel_x1+j, sel_y1+i, width, height, TRUE, TRUE); merge_region (&front_rgn, &back_rgn, &dest_rgn); } } } static void multitile (gint32 image_id, gint32 drawable_id) { GimpPixelRgn comp_tile_rgn; GimpDrawable *comp_tile_drawable, *org_tile_drawable; gint32 comp_tile_image_id, comp_tile_layer_id; gint32 org_tile_image_id, org_tile_layer_id; GimpImageBaseType image_type; gint img_sel_x1, img_sel_y1; gint img_sel_width, img_sel_height; gint pre_sel_x1, pre_sel_y1; gint pre_sel_width, pre_sel_height; gint comp_tile_width, comp_tile_height; gpointer pr; guchar *dest_row, *dest; guchar pixel[4]; gint i, row, col; gint nc, nr; gint off_x, off_y; gint fea_radius_x, fea_radius_y, fea_radius; gint nreturn_vals; /* initialize */ gimp_progress_init ("multitile..."); image_type = GIMP_RGB; /* calculate the selected image area */ pre_sel_x1 = tint.selection.points[0].x; pre_sel_y1 = tint.selection.points[0].y; pre_sel_width = tint.selection.points[1].x - pre_sel_x1; pre_sel_height = tint.selection.points[1].y - pre_sel_y1; img_sel_x1 = sel_x1 + (gint) (scale_x_factor * pre_sel_x1); img_sel_y1 = sel_y1 + (gint) (scale_y_factor * pre_sel_y1); img_sel_width = (gint) (scale_x_factor * pre_sel_width); img_sel_height= (gint) (scale_y_factor * pre_sel_height); fea_radius_x = (gint) ((img_sel_width * feather) / (2 * 100)); fea_radius_y = (gint) ((img_sel_height * feather) / (2 * 100)); fea_radius = MIN (fea_radius_x, fea_radius_y); comp_tile_width = 2 * img_sel_width; comp_tile_height = 2 * img_sel_height; progress = 0; max_progress = comp_tile_width * comp_tile_height + sel_width * sel_height; if (feather) max_progress += img_sel_width * img_sel_height; /* set gimp tile cache number*/ gimp_tile_cache_ntiles (2 * ((drawable->width) / tile_width + 1)); gimp_run_procedure ("gimp_undo_push_group_start", &nreturn_vals, GIMP_PDB_IMAGE, image_id, GIMP_PDB_END); switch (gimp_drawable_type (drawable_id)) { case GIMP_RGB_IMAGE : case GIMP_RGBA_IMAGE: image_type = GIMP_RGB; break; case GIMP_GRAY_IMAGE : case GIMP_GRAYA_IMAGE: image_type = GIMP_GRAY; break; default: break; } /* built org_tile_image*/ if (feather) { org_tile_image_id = gimp_image_new (img_sel_width, img_sel_height, image_type); org_tile_layer_id = gimp_layer_new (org_tile_image_id, "Background", img_sel_width, img_sel_height, gimp_drawable_type (drawable_id), 100, GIMP_NORMAL_MODE); gimp_image_add_layer (org_tile_image_id, org_tile_layer_id, 0); org_tile_drawable = gimp_drawable_get (org_tile_layer_id); make_org_tile (drawable, img_sel_x1, img_sel_y1, org_tile_drawable, fea_radius, fea_radius); off_x = 0; off_y = 0; } else { off_x = img_sel_x1; off_y = img_sel_y1; org_tile_drawable = drawable; } /* built comp_tile_rgn in the comp_tile image*/ comp_tile_image_id = gimp_image_new (comp_tile_width, comp_tile_height, image_type); comp_tile_layer_id = gimp_layer_new (comp_tile_image_id, "Background", comp_tile_width, comp_tile_height, gimp_drawable_type (drawable_id), 100, GIMP_NORMAL_MODE); gimp_image_add_layer (comp_tile_image_id, comp_tile_layer_id, 0); comp_tile_drawable = gimp_drawable_get (comp_tile_layer_id); gimp_pixel_rgn_init (&comp_tile_rgn, comp_tile_drawable, 0, 0, comp_tile_width, comp_tile_height, TRUE, TRUE); for (pr = gimp_pixel_rgns_register(1, &comp_tile_rgn); pr != NULL; pr = gimp_pixel_rgns_process(pr)) { dest_row = comp_tile_rgn.data; for (row = comp_tile_rgn.y; row < (comp_tile_rgn.y + comp_tile_rgn.h); row++) { dest = dest_row; for (col = comp_tile_rgn.x; col < (comp_tile_rgn.x + comp_tile_rgn.w); col++) { switch (tint.symmetry_type) { case HONEYBEE: honeybee_tiles_xy (img_sel_width, img_sel_height, col, row, &nc, &nr); break; case PINWHEEL: pinwheel_tiles_xy (img_sel_width, img_sel_height, col, row , &nc, &nr); break; case GOLDBRICK: goldbrick_tiles_xy (img_sel_width, img_sel_height, col, row , &nc, &nr); break; case CRABCLAWS: crabclaws_tiles_xy (img_sel_width, img_sel_height, col, row, &nc, &nr); break; case PRICKLYPEAR: pricklypear_tiles_xy (img_sel_width, img_sel_height, col, row , &nc, &nr); break; case SUNFLOWER: sunflower_tiles_xy (img_sel_width, img_sel_height, col, row , &nc, &nr); break; default: break; } tile_get_pixel (off_x + nc, off_y + nr, org_tile_drawable, pixel); for (i = 0; i < img_bpp; i++) *dest++ = pixel[i]; } dest_row += comp_tile_rgn.rowstride; } /* Update progress */ progress += comp_tile_rgn.w * comp_tile_rgn.h; gimp_progress_update ((double) progress / (double) max_progress); } gimp_drawable_flush(comp_tile_drawable); gimp_drawable_merge_shadow (comp_tile_drawable->id, TRUE); /* copy comp_tile_rgn to the selection area */ tile (img_sel_x1, img_sel_y1, comp_tile_drawable, drawable); gimp_run_procedure ("gimp_undo_push_group_end", &nreturn_vals, GIMP_PDB_IMAGE, image_id, GIMP_PDB_END); gimp_drawable_flush(drawable); gimp_drawable_merge_shadow (drawable->id, TRUE); gimp_drawable_update(drawable->id, sel_x1, sel_y1, sel_width, sel_height); if (feather) { gimp_drawable_detach (org_tile_drawable); gimp_image_delete (org_tile_image_id); } gimp_drawable_detach (comp_tile_drawable); gimp_image_delete (comp_tile_image_id); } /* Cache the preview image - updates are a lot faster. */ /* The preview_cache will contain the small image */ static void cache_preview() { GimpPixelRgn src_rgn; int y,x; guchar *src_rows; guchar *p, *q; int isgrey = 0; gimp_pixel_rgn_init(&src_rgn,drawable,sel_x1,sel_y1,sel_width,sel_height,FALSE,FALSE); src_rows = g_new(guchar ,sel_width*4); p = tint.bef_pv_cache = g_new(guchar ,preview_width*preview_height*4); q = tint.aft_pv_cache = g_new(guchar ,preview_width*preview_height*4); tint.img_bpp = img_bpp; if(tint.img_bpp < 3) { tint.img_bpp = 3 + has_alpha; } switch ( gimp_drawable_type (drawable->id) ) { case GIMP_GRAYA_IMAGE: case GIMP_GRAY_IMAGE: isgrey = 1; break; default: isgrey = 0; break; } for (y = 0; y < preview_height; y++) { gimp_pixel_rgn_get_row(&src_rgn, src_rows, sel_x1, sel_y1 + (y*sel_height)/preview_height, sel_width); for (x = 0; x < (preview_width); x ++) { /* Get the pixels of each col */ int i; for (i = 0 ; i < 3; i++ ) q[x*tint.img_bpp+i] = p[x*tint.img_bpp+i] = src_rows[((x*sel_width)/preview_width)*src_rgn.bpp +((isgrey)?0:i)]; if(has_alpha) q[x*tint.img_bpp+3] = p[x*tint.img_bpp+3] = src_rows[((x*sel_width)/preview_width)*src_rgn.bpp + ((isgrey)?1:3)]; } p += (preview_width*tint.img_bpp); q += (preview_width*tint.img_bpp); } g_free(src_rows); } static gint main_dialog ( gint32 image, gint32 drawable ) { GtkWidget *dlg; GtkWidget *label; GtkWidget *button; GtkWidget *frame; GtkWidget *lframe, *rframe; GtkWidget *table; GtkWidget *slider; GtkObject *opacity_adjustment; GtkObject *feather_adjustment; char buffer[12]; guchar *color_cube; GtkWidget *option_menu; GtkWidget *menu; GtkWidget *menuitem; gchar **argv; gint argc; argc = 1; argv = g_new (gchar *, 1); argv[0] = g_strdup ("multitile"); gtk_init (&argc, &argv); gtk_rc_parse (gimp_gtkrc ()); /* Get the stuff for the preview window...*/ gtk_preview_set_gamma(gimp_gamma()); gtk_preview_set_install_cmap(gimp_install_cmap()); color_cube = gimp_color_cube(); gtk_preview_set_color_cube(color_cube[0], color_cube[1], color_cube[2], color_cube[3]); gtk_widget_set_default_visual(gtk_preview_get_visual()); gtk_widget_set_default_colormap(gtk_preview_get_cmap()); /* Get the preview image and store it also set has_alpha */ cache_preview(); dlg = gtk_dialog_new (); gtk_window_set_title (GTK_WINDOW (dlg), "Multitile"); gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE); gtk_signal_connect (GTK_OBJECT (dlg), "destroy", (GtkSignalFunc) tile_close_callback, NULL); tooltips = gtk_tooltips_new (); /* Action area */ runit.image = image; runit.drawable = drawable; runit.dialog = dlg; button = gtk_button_new_with_label ("OK"); gtk_tooltips_set_tip(tooltips, button, "Click if You are done", NULL); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) tile_ok_callback, (gpointer)&runit); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0); gtk_widget_grab_default (button); gtk_widget_show (button); button = gtk_button_new_with_label ("Cancel"); gtk_tooltips_set_tip(tooltips, button, "Close filter without doing anything", NULL); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) tile_cancel_callback, dlg); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0); gtk_widget_show (button); /* Start building the frame for the preview area */ frame = gtk_frame_new ("preview"); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); gtk_container_border_width (GTK_CONTAINER (frame), 1); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, TRUE, TRUE, 0); table = gtk_table_new (4, 6, FALSE); gtk_container_border_width (GTK_CONTAINER (table), 1); gtk_container_add (GTK_CONTAINER (frame), table); lframe = gtk_frame_new ("Original_preview"); gtk_frame_set_shadow_type (GTK_FRAME (lframe), GTK_SHADOW_ETCHED_IN); gtk_container_border_width (GTK_CONTAINER (lframe), 1); gtk_table_attach(GTK_TABLE(table), lframe, 0, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0); rframe = gtk_frame_new ("After_preview"); gtk_frame_set_shadow_type (GTK_FRAME (rframe), GTK_SHADOW_ETCHED_IN); gtk_container_border_width (GTK_CONTAINER (rframe), 1); gtk_table_attach(GTK_TABLE(table), rframe, 3, 6, 0, 1, GTK_FILL, GTK_FILL, 0, 0); /* Preview itself */ tint.bef_preview = gtk_preview_new(GTK_PREVIEW_COLOR); gtk_widget_set_events( GTK_WIDGET(tint.bef_preview), PREVIEW_MASK ); tint.bef_preview_exp_id = gtk_signal_connect_after( GTK_OBJECT(tint.bef_preview), "expose_event", (GtkSignalFunc) bef_preview_expose, NULL); gtk_signal_connect( GTK_OBJECT(tint.bef_preview), "event", (GtkSignalFunc) bef_preview_events, NULL); gtk_preview_size(GTK_PREVIEW(tint.bef_preview), preview_width, preview_height); gtk_container_add (GTK_CONTAINER (lframe), tint.bef_preview); gtk_widget_show(tint.bef_preview); gtk_widget_show(lframe); tint.aft_preview = gtk_preview_new(GTK_PREVIEW_COLOR); gtk_preview_size(GTK_PREVIEW(tint.aft_preview), preview_width, preview_height); gtk_container_add (GTK_CONTAINER (rframe), tint.aft_preview); gtk_widget_show(tint.aft_preview); gtk_widget_show(rframe); /* Widget for selecting the Opacity */ label = gtk_label_new ("Opacity: "); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); opacity_adjustment = gtk_adjustment_new (100, 0, 100, 1, 1, 0); slider = gtk_hscale_new (GTK_ADJUSTMENT (opacity_adjustment)); gtk_widget_set_usize (slider, SCALE_WIDTH, 0); gtk_table_attach (GTK_TABLE (table), slider, 2, 4, 1, 2, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); gtk_scale_set_value_pos (GTK_SCALE (slider), GTK_POS_LEFT); gtk_scale_set_digits (GTK_SCALE (slider), 0); gtk_range_set_update_policy (GTK_RANGE (slider),GTK_UPDATE_CONTINUOUS ); gtk_signal_connect (GTK_OBJECT (opacity_adjustment), "value_changed", (GtkSignalFunc) multitile_scale_update, &opacity); gtk_widget_show (slider); tint.opacity_entry = gtk_entry_new(); gtk_widget_set_usize (tint.opacity_entry, ENTRY_WIDTH, ENTRY_HEIGHT); gtk_object_set_user_data(GTK_OBJECT(tint.opacity_entry), opacity_adjustment); gtk_object_set_user_data(opacity_adjustment, tint.opacity_entry); sprintf(buffer, "%.1d", opacity); gtk_entry_set_text(GTK_ENTRY(tint.opacity_entry), buffer); gtk_signal_connect(GTK_OBJECT(tint.opacity_entry), "changed", (GtkSignalFunc) multitile_entry_update, &opacity); gtk_table_attach(GTK_TABLE(table), tint.opacity_entry, 4, 6, 1, 2, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show(tint.opacity_entry); /* Widget for feathering */ label = gtk_label_new ("Feather: "); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); feather_adjustment = gtk_adjustment_new (0, 0, 100, 1, 1, 0); tint.feather_slider = gtk_hscale_new (GTK_ADJUSTMENT (feather_adjustment)); gtk_widget_set_usize (tint.feather_slider, SCALE_WIDTH, 0); gtk_table_attach (GTK_TABLE (table), tint.feather_slider, 2, 4, 2, 3, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); gtk_scale_set_value_pos (GTK_SCALE (tint.feather_slider), GTK_POS_LEFT); gtk_scale_set_digits (GTK_SCALE (tint.feather_slider), 0); gtk_range_set_update_policy (GTK_RANGE (tint.feather_slider),GTK_UPDATE_CONTINUOUS ); gtk_signal_connect (GTK_OBJECT (feather_adjustment), "value_changed", (GtkSignalFunc) multitile_scale_update, &feather); gtk_widget_show (tint.feather_slider); tint.feather_entry = gtk_entry_new(); gtk_widget_set_usize (tint.feather_entry, ENTRY_WIDTH, ENTRY_HEIGHT); gtk_object_set_user_data(GTK_OBJECT(tint.feather_entry), feather_adjustment); gtk_object_set_user_data(feather_adjustment, tint.feather_entry); sprintf(buffer, "%.1d", feather); gtk_entry_set_text(GTK_ENTRY(tint.feather_entry), buffer); gtk_signal_connect(GTK_OBJECT(tint.feather_entry), "changed", (GtkSignalFunc) multitile_entry_update, &feather); gtk_table_attach(GTK_TABLE(table), tint.feather_entry, 4, 6, 2, 3, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show(tint.feather_entry); /* Widget for symmetry */ label = gtk_label_new ("Symmetry"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); option_menu = gtk_option_menu_new (); gtk_tooltips_set_tip(tooltips,option_menu, "Select the Symmetry from the list", NULL); menu = gtk_menu_new(); menuitem = gtk_menu_item_new_with_label("Honey Bee"); gtk_object_set_user_data (GTK_OBJECT (menuitem), (gpointer) HONEYBEE); gtk_signal_connect (GTK_OBJECT (menuitem), "activate", (GtkSignalFunc) symmetry_menu_callback, NULL); gtk_widget_show (menuitem); gtk_menu_append (GTK_MENU (menu), menuitem); menuitem = gtk_menu_item_new_with_label("Pin Wheel"); gtk_object_set_user_data (GTK_OBJECT (menuitem), (gpointer) PINWHEEL); gtk_signal_connect (GTK_OBJECT (menuitem), "activate", (GtkSignalFunc) symmetry_menu_callback, NULL); gtk_widget_show (menuitem); gtk_menu_append (GTK_MENU (menu), menuitem); menuitem = gtk_menu_item_new_with_label("Gold Brick"); gtk_object_set_user_data (GTK_OBJECT (menuitem), (gpointer) GOLDBRICK); gtk_signal_connect (GTK_OBJECT (menuitem), "activate", (GtkSignalFunc) symmetry_menu_callback, NULL); gtk_widget_show (menuitem); gtk_menu_append (GTK_MENU (menu), menuitem); menuitem = gtk_menu_item_new_with_label("Crab Claws"); gtk_object_set_user_data (GTK_OBJECT (menuitem), (gpointer) CRABCLAWS); gtk_signal_connect (GTK_OBJECT (menuitem), "activate", (GtkSignalFunc) symmetry_menu_callback, NULL); gtk_widget_show (menuitem); gtk_menu_append (GTK_MENU (menu), menuitem); menuitem = gtk_menu_item_new_with_label("Prickly Pear"); gtk_object_set_user_data (GTK_OBJECT (menuitem), (gpointer) PRICKLYPEAR); gtk_signal_connect (GTK_OBJECT (menuitem), "activate", (GtkSignalFunc) symmetry_menu_callback, NULL); gtk_widget_show (menuitem); gtk_menu_append (GTK_MENU (menu), menuitem); menuitem = gtk_menu_item_new_with_label("Sun Flower"); gtk_object_set_user_data (GTK_OBJECT (menuitem), (gpointer) SUNFLOWER); gtk_signal_connect (GTK_OBJECT (menuitem), "activate", (GtkSignalFunc) symmetry_menu_callback, NULL); gtk_widget_show (menuitem); gtk_menu_append (GTK_MENU (menu), menuitem); gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), menu); gtk_widget_show (option_menu); gtk_table_attach (GTK_TABLE (table), option_menu, 2, 6, 3, 4, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (table); gtk_widget_show (frame); gtk_widget_show (dlg); new_gc(); create_selection ( ); dialog_update_preview(); gtk_main (); gdk_flush (); return tint.run; } /* Callback functions */ static void tile_close_callback (GtkWidget *widget, gpointer data) { gtk_main_quit (); } static void tile_cancel_callback(GtkWidget *widget, gpointer data) { gtk_widget_destroy(GTK_WIDGET(data)); } static void tile_ok_callback (GtkWidget *widget, gpointer data) { tint.run = TRUE; multitile(runit.image, runit.drawable); gtk_widget_destroy (GTK_WIDGET (runit.dialog)); } static void multitile_entry_update (GtkWidget *widget, gpointer data) { GtkAdjustment *adjustment; gint new_val; gint *entry_val; new_val = atoi(gtk_entry_get_text(GTK_ENTRY(widget))); entry_val = (gint *) data; if (((widget == tint.opacity_entry) || (widget == tint.feather_entry)) && *entry_val != new_val) { adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); if ((new_val >= adjustment->lower) && (new_val <= adjustment->upper)) { *entry_val = new_val; adjustment->value = new_val; gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); dialog_update_preview(); } } } static void multitile_scale_update (GtkAdjustment *adjustment, gpointer data) { GtkWidget *entry; char buf[12]; gint *val; val = (gint *) data; if (*val != adjustment->value) { *val = adjustment->value; entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); sprintf(buf,"%d",*val); gtk_signal_handler_block_by_data(GTK_OBJECT(entry), val); gtk_entry_set_text(GTK_ENTRY(entry), buf); gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), val); dialog_update_preview(); } } static gint bef_preview_expose (GtkWidget *widget, GdkEvent *event ) { GdkCursor *preview_cursor; static gint changed_cursor = 0; if(!changed_cursor && tint.bef_preview->window) { changed_cursor = 1; preview_cursor = gdk_cursor_new(GDK_CROSSHAIR); gdk_window_set_cursor(tint.bef_preview->window,preview_cursor); } update_draw_area(widget,event); return FALSE; } static gint bef_preview_events ( GtkWidget *widget, GdkEvent *event ) { GdkEventButton *bevent; GdkEventMotion *mevent; GdkPoint point; switch (event->type) { case GDK_EXPOSE: break; case GDK_BUTTON_PRESS: bevent = (GdkEventButton *) event; point.x = bevent->x; point.y = bevent->y; if (inside_control_square (point)) { tint.otype= RESIZE; tint.move_start_point = point; } else { if (tint.selection.inside_func (point, tint.selection.points)) { tint.otype= MOVE; tint.move_start_point = point; } } break; case GDK_BUTTON_RELEASE: bevent = (GdkEventButton *) event; point.x = bevent->x; point.y = bevent->y; switch (tint.otype) { case MOVE: case RESIZE: tint.otype = NULL_OPER; dialog_update_preview( ); break; default: break; } case GDK_MOTION_NOTIFY: mevent = (GdkEventMotion *) event; point.x = mevent->x; point.y = mevent->y; switch(tint.otype) { case MOVE: tint.selection.move_func (&point); break; case RESIZE: tint.selection.resize_func (&point); break; default: /*g_warning("Internal error otype");*/ break; } break; default: break; } return FALSE; } static void new_gc() { GdkColor fg, bg; /* create a new graphics context */ tint.gui_gc = gdk_gc_new (tint.bef_preview->window); gdk_gc_set_function (tint.gui_gc, GDK_INVERT); fg.pixel = 0xFFFFFFFF; bg.pixel = 0x00000000; gdk_gc_set_foreground (tint.gui_gc, &fg); gdk_gc_set_background (tint.gui_gc, &bg); gdk_gc_set_line_attributes (tint.gui_gc, 1, GDK_LINE_SOLID,GDK_CAP_BUTT,GDK_JOIN_MITER); } static gint inside_control_square (GdkPoint pnt) { return (pnt.x >= tint.selection.points[1].x - SQ_SIZE/2) && (pnt.x <= tint.selection.points[1].x + SQ_SIZE/2) && (pnt.y >= tint.selection.points[1].y - SQ_SIZE/2) && (pnt.y <= tint.selection.points[1].y + SQ_SIZE/2); } static void get_diffs (gint16 *xdiff, gint16 *ydiff, GdkPoint *to_pnt, GdkPoint *from_pnt) { *xdiff = from_pnt->x - to_pnt->x; *ydiff = from_pnt->y - to_pnt->y; } static void update_draw_area(GtkWidget *widget,GdkEvent *event) { if (!GTK_WIDGET_DRAWABLE(widget)) return; gtk_signal_handler_block(GTK_OBJECT(widget),tint.bef_preview_exp_id); gtk_widget_draw(widget, NULL); gtk_signal_handler_unblock(GTK_OBJECT(widget),tint.bef_preview_exp_id ); tint.selection.draw_func ( ); } static void dialog_update_preview (void) { int y; gint check,check_0,check_1; gint count; guchar *pv_cache; GtkWidget *preview; update_preview_cache ( ); for (count = 0; count < 2; count++) { if (count == 0) { preview = tint.bef_preview; pv_cache = tint.bef_pv_cache; } else { preview = tint.aft_preview; pv_cache = tint.aft_pv_cache; } for (y = 0; y < preview_height; y++) { if ((y / CHECK_SIZE) & 1) { check_0 = CHECK_DARK; check_1 = CHECK_LIGHT; } else { check_0 = CHECK_LIGHT; check_1 = CHECK_DARK; } do_preview (tint.preview_row, pv_cache + y * preview_width * tint.img_bpp, preview_width, y, preview_height, tint.img_bpp); if(tint.img_bpp > 3) { int i,j; for (i = 0, j = 0 ; i < sizeof(tint.preview_row); i += 4, j += 3 ) { gint alphaval; if (((i/4) / CHECK_SIZE) & 1) check = check_0; else check = check_1; alphaval = tint.preview_row[i + 3]; tint.preview_row[j] = check + (((tint.preview_row[i] - check)*alphaval)/255); tint.preview_row[j + 1] = check + (((tint.preview_row[i + 1] - check)*alphaval)/255); tint.preview_row[j + 2] = check + (((tint.preview_row[i + 2] - check)*alphaval)/255); } } gtk_preview_draw_row(GTK_PREVIEW(preview), tint.preview_row, 0, y, preview_width); } } gtk_widget_draw(tint.bef_preview, NULL); gtk_widget_draw(tint.aft_preview, NULL); gdk_flush(); } static void make_prv_fea_cache (guchar *src_cache, guchar *dest_cache, gint src_width, gint x1, gint y1, gint width, gint height, gint dx, gint dy) { gint fea_rgn_width, fea_rgn_height; gint nx[2], ny[2]; gint num, j, k; gint alpha; guchar back, dest; guchar pixel[8]; gint row, col; gint cen_x, cen_y; gdouble radius, d; gdouble dist[2], back_opa, dist_sum; gdouble temp; fea_rgn_width = width + 2 * dx; fea_rgn_height = height + 2 * dy; cen_x = width / 2; cen_y = height / 2; radius = pnts_dist (0, 0, cen_x, cen_y); switch (tint.symmetry_type) { case HONEYBEE: for (row = 0; row < height; row++) { for (col = 0; col < width; col++) { num = 0; d = pnts_dist (col, row, cen_x, cen_y); back_opa = 1 - d / radius; if (col < cen_x) { nx[num] = col + width; ny[num] = height - row ; num++; } else { nx[num] = col - width; ny[num] = height - row; num++; } if (row < cen_y) { nx[num] = width - col; ny[num] = row + height; num++; } else { nx[num] = width - col; ny[num] = row - height; num++; } dist_sum = 0; for (j = 0; j < num; j++) { if (nx[j] < -dx) nx[j] = -dx; else if (nx[j] >= width + dx) nx[j] = width + dx - 1; if (ny[j] < -dy) ny[j] = -dy; else if (ny[j] >= height + dy) ny[j] = height + dy - 1; for (k = 0 ; k < tint.img_bpp; k++ ) pixel[j * tint.img_bpp + k] = src_cache[((ny[j] + y1) * src_width + (nx[j] + x1)) * tint.img_bpp + k]; dist[j] = pnts_dist (nx[j], ny[j],cen_x, cen_y); dist_sum += dist[j]; } if (has_alpha) { alpha = src_cache[((row + y1) * src_width + (col + x1)) * tint.img_bpp + tint.img_bpp -1]; for (k = 0; k < tint.img_bpp - 1; k++) { multiply_alpha (pixel, num, tint.img_bpp); temp = 0; for (j = 0 ; j < num; j++) { temp += (1 - back_opa) * pixel[j * tint.img_bpp + k] * (1 - dist[j] / dist_sum); } back = src_cache[((row + y1) * src_width + (col + x1)) * tint.img_bpp + k]; temp += back_opa * back * alpha/ 255; dest = (gint) (temp * 255 / alpha); dest_cache[(row * width + col) * tint.img_bpp + k] = MIN (255, dest); } dest_cache[(row * width + col) * tint.img_bpp + tint.img_bpp -1] = alpha; } else { for (k = 0; k < tint.img_bpp; k++) { temp = 0; for (j = 0 ; j < num; j++) { temp += (1 - back_opa) * pixel[j * tint.img_bpp + k] * (1 - dist[j] / dist_sum); } back = src_cache[((row + y1) * src_width + (col + x1)) * tint.img_bpp + k]; temp += back * back_opa; dest = (gint) (temp); dest_cache[(row * width + col) * tint.img_bpp + k] = MIN (255, dest); } /* for */ } /* else */ }/* for col */ } /* for row */ break; default: break; } /* switch */ } static void update_preview_cache ( ) { guchar *comp_tile_cache, *org_tile_cache; gint pre_sel_width, pre_sel_height; gint pre_sel_x1, pre_sel_y1; gint comp_tile_width, comp_tile_height; gint old_width, old_height; gint width, height; gint x, y; gint i,j; gint nc, nr; gint off_x, off_y; pre_sel_x1 = tint.selection.points[0].x; pre_sel_y1 = tint.selection.points[0].y; pre_sel_width = tint.selection.points[1].x - pre_sel_x1; pre_sel_height = tint.selection.points[1].y - pre_sel_y1; comp_tile_width = 2 * pre_sel_width; comp_tile_height = 2 * pre_sel_height; if (feather) { gint fea_radius; org_tile_cache = g_malloc(pre_sel_width * pre_sel_height * 4 * sizeof(guchar)); fea_radius = MIN (pre_sel_width * feather / (2 * 100), pre_sel_height * feather / (2 * 100)); make_prv_fea_cache (tint.bef_pv_cache, org_tile_cache, preview_width, pre_sel_x1, pre_sel_y1, pre_sel_width, pre_sel_height, fea_radius, fea_radius); off_x = 0; off_y = 0; width = pre_sel_width; } /* if */ else { off_x = pre_sel_x1; off_y = pre_sel_y1; width = preview_width; org_tile_cache = tint.bef_pv_cache; } comp_tile_cache = g_malloc (comp_tile_width * comp_tile_height * 4 * sizeof(guchar)); for (y = 0; y < comp_tile_height; y++) { for (x = 0; x < comp_tile_width; x++) { switch (tint.symmetry_type) { case HONEYBEE: honeybee_tiles_xy (pre_sel_width, pre_sel_height, x, y, &nc, &nr); break; case PINWHEEL: pinwheel_tiles_xy (pre_sel_width, pre_sel_height, x, y, &nc, &nr); break; case GOLDBRICK: goldbrick_tiles_xy (pre_sel_width, pre_sel_height, x, y, &nc, &nr); break; case CRABCLAWS: crabclaws_tiles_xy (pre_sel_width, pre_sel_height, x, y, &nc, &nr); break; case SUNFLOWER: sunflower_tiles_xy (pre_sel_width, pre_sel_height, x, y, &nc, &nr); break; case PRICKLYPEAR: pricklypear_tiles_xy (pre_sel_width, pre_sel_height, x, y, &nc, &nr); break; default: break; } nr += off_y; nc += off_x; for (i = 0 ; i < tint.img_bpp; i++ ) comp_tile_cache[(y * comp_tile_width + x) * tint.img_bpp + i] = org_tile_cache[(nr * width + nc) * tint.img_bpp + i]; } /* col */ } /* row */ /* tile... */ old_width = comp_tile_width; old_height = comp_tile_height; /* tile the top left part... */ for (i = pre_sel_y1; i >= 0; i -= old_height) { height = old_height; if (i - height < 0) height = i; for (j = pre_sel_x1; j >= 0; j -= old_width) { width = old_width; if (j - width < 0) width = j; merge_preview_cache_region (j - width, i - height, old_width - width, old_height - height, width, height, comp_tile_width, comp_tile_height, comp_tile_cache); } } /* tile the top right part... */ for (i = pre_sel_y1; i >= 0; i -= old_height) { height = old_height; if (i - height < 0) height = i; for (j = pre_sel_x1; j < preview_width; j += old_width) { width = old_width; if (j + width > preview_width) width = preview_width - j; merge_preview_cache_region (j, i - height, 0, old_height - height, width, height, comp_tile_width, comp_tile_height, comp_tile_cache); } } /* tile the bottom left part... */ for (i = pre_sel_y1; i < preview_height; i += old_height) { height = old_height; if (i + height > preview_height) height = preview_height - i; for (j = pre_sel_x1; j >= 0; j -= old_width) { width = old_width; if (j - width < 0) width = j; merge_preview_cache_region (j - width, i, old_width-width, 0, width, height, comp_tile_width, comp_tile_height, comp_tile_cache); } } /* tile the bottom right part... */ for (i = pre_sel_y1; i < preview_height; i += old_height) { height = old_height; if (height + i > preview_height) height = preview_height - i; for (j = pre_sel_x1; j < preview_width; j += old_width) { width = old_width; if (width + j > preview_width) width = preview_width - j; merge_preview_cache_region (j, i, 0, 0, width, height, comp_tile_width, comp_tile_height, comp_tile_cache); } } g_free (comp_tile_cache); if (feather) g_free (org_tile_cache); } static void do_preview (guchar *dest_row, guchar *src_row, gint width, gint dh, gint height, gint bpp) { memcpy(dest_row,src_row,width*bpp); } static void symmetry_menu_callback (GtkWidget *widget, gpointer data) { tint.symmetry_type = (SYMMETRYTYPE)gtk_object_get_user_data (GTK_OBJECT (widget)); /* undraw first */ tint.selection.draw_func ( ); /* creat new selection and draw it */ create_selection ( ); switch (tint.symmetry_type) { case HONEYBEE: gtk_widget_set_sensitive(tint.feather_entry,TRUE); gtk_widget_set_sensitive(tint.feather_slider,TRUE); break; // Addin these codes has no effect at all //PRaveen /* case PINWHEEL: gtk_widget_set_sensitive(tint.feather_entry,TRUE); gtk_widget_set_sensitive(tint.feather_slider,TRUE); break; */ default: gtk_widget_set_sensitive(tint.feather_entry,FALSE); gtk_widget_set_sensitive(tint.feather_slider,FALSE); feather = 0; break; } dialog_update_preview ( ); } static void create_selection ( ) { switch (tint.symmetry_type) { case HONEYBEE: tint.selection.type = RECTANGLE; tint.selection.draw_func = draw_rectangle; tint.selection.inside_func = inside_rectangle; tint.selection.resize_func = resize_rectangle; break; case PRICKLYPEAR: tint.selection.type = RECTANGLE; tint.selection.draw_func = draw_rectangle; tint.selection.inside_func = inside_rectangle; tint.selection.resize_func = resize_rectangle; break; case PINWHEEL: tint.selection.type = SQUARE; tint.selection.draw_func = draw_rectangle; tint.selection.inside_func = inside_rectangle; tint.selection.resize_func = resize_square; break; case GOLDBRICK: tint.selection.type = SQUARE; tint.selection.draw_func = draw_rectangle; tint.selection.inside_func = inside_rectangle; tint.selection.resize_func = resize_square; break; case CRABCLAWS: tint.selection.type = TRIANGLE; tint.selection.draw_func = draw_triangle; tint.selection.inside_func = inside_triangle; tint.selection.resize_func = resize_triangle; break; case SUNFLOWER: tint.selection.type = TRIANGLE; tint.selection.draw_func = draw_triangle; tint.selection.inside_func = inside_triangle; tint.selection.resize_func = resize_triangle; break; default: break; } tint.selection.move_func = move_sel_obj; tint.selection.points[0].x = tint.selection.points[0].y = 1; tint.selection.points[1].x = tint.selection.points[1].y = MIN(preview_width/2, preview_height/2); tint.selection.draw_func ( ); } static void draw_rectangle ( ) { GdkPoint pnt; gint width, height; pnt.x = tint.selection.points[0].x; pnt.y = tint.selection.points[0].y; width = tint.selection.points[1].x - pnt.x; height= tint.selection.points[1].y - pnt.y; gdk_draw_rectangle (tint.bef_preview->window, tint.gui_gc, 0, (gint) pnt.x, (gint) pnt.y, (gint) width, (gint) height); draw_control_square ( ); } static void draw_triangle ( ) { gint i; GdkPoint pnts[3]; pnts[0] = tint.selection.points[0]; pnts[1] = tint.selection.points[1]; pnts[2].x = tint.selection.points[0].x; pnts[2].y = tint.selection.points[1].y; for (i = 0; i < 3; i++) gdk_draw_line (tint.bef_preview->window, tint.gui_gc, pnts[i].x, pnts[i].y, pnts[(i+1)%3].x, pnts[(i+1)%3].y); draw_control_square ( ); } static void draw_control_square ( ) { gdk_draw_rectangle (tint.bef_preview->window, tint.gui_gc, 0, tint.selection.points[1].x - SQ_SIZE/2, tint.selection.points[1].y - SQ_SIZE/2, SQ_SIZE, SQ_SIZE); } static void move_sel_obj (GdkPoint *to_pnt) { gint16 xdiff = 0; gint16 ydiff = 0; get_diffs(&xdiff,&ydiff,to_pnt, &tint.move_start_point); if(!xdiff && !ydiff) return; /* undraw ! */ tint.selection.draw_func ( ); /* update points */ update_pnts (xdiff,ydiff); /* Draw in new pos */ tint.selection.draw_func ( ); tint.move_start_point = *to_pnt; } static void update_pnts(gint16 xdiff, gint16 ydiff) { gint i; for (i = 0; i < 2; i++) { tint.selection.points[i].x -=xdiff; tint.selection.points[i].y -=ydiff; } } static void resize_square (GdkPoint *to_pnt) { gint16 xdiff = 0; gint16 ydiff = 0; gint16 new_x, new_y; get_diffs(&xdiff, &ydiff, to_pnt, &tint.move_start_point); if(!xdiff && !ydiff) return; new_x = tint.selection.points[1].x - xdiff; new_y = tint.selection.points[1].y - xdiff; if (new_x <= tint.selection.points[0].x || new_y <= tint.selection.points[0].y ) return; /* undraw ! */ tint.selection.draw_func ( ); /* update points */ tint.selection.points[1].x = new_x; tint.selection.points[1].y = new_y; /* Draw in new pos */ tint.selection.draw_func ( ); tint.move_start_point = *to_pnt; } static void resize_triangle (GdkPoint *to_pnt) { gint16 xdiff = 0; gint16 ydiff = 0; gint16 new_x, new_y; get_diffs(&xdiff, &ydiff, to_pnt, &tint.move_start_point); if(!xdiff && !ydiff) return; new_x = tint.selection.points[1].x - xdiff; new_y = tint.selection.points[1].y - xdiff; if (new_x <= tint.selection.points[0].x || new_y <= tint.selection.points[0].y ) return; /* undraw ! */ tint.selection.draw_func ( ); /* update points */ tint.selection.points[1].x = new_x; tint.selection.points[1].y = new_y; /* Draw in new pos */ tint.selection.draw_func ( ); tint.move_start_point = *to_pnt; } static void resize_rectangle (GdkPoint *to_pnt) { gint16 xdiff = 0; gint16 ydiff = 0; gint16 new_x, new_y; get_diffs(&xdiff, &ydiff, to_pnt, &tint.move_start_point); if(!xdiff && !ydiff) return; new_x = tint.selection.points[1].x - xdiff; new_y = tint.selection.points[1].y - ydiff; if (new_x <= tint.selection.points[0].x || new_y <= tint.selection.points[0].y ) return; /* undraw ! */ tint.selection.draw_func ( ); /* update points */ tint.selection.points[1].x = new_x; tint.selection.points[1].y = new_y; /* Draw in new pos */ tint.selection.draw_func ( ); tint.move_start_point = *to_pnt; } static gint inside_rectangle (GdkPoint pnt, GdkPoint *rect) { return (pnt.x >= rect[0].x) && (pnt.x <= rect[1].x) && (pnt.y >= rect[0].y) && (pnt.y <= rect[1].y); } static gint inside_triangle (GdkPoint pnt, GdkPoint *tri) { gint x1, y1, x2, y2; gint off_x, off_y; x1 = tri[0].x; y1 = tri[0].y; x2 = tri[1].x; y2 = tri[1].y; off_x = pnt.x - x1; off_y = pnt.y - y1; return (off_x <= off_y) && (pnt.x >= x1) && (pnt.x < x2) && (pnt.y >= y1) && (pnt.y < y2); } /* Convert from separated to premultiplied alpha, on a single scan line. */ static void multiply_alpha (guchar *buf, gint width, gint bytes) { gint i, j; gdouble alpha; for (i = 0; i < width * bytes; i += bytes) { alpha = buf[i + bytes - 1] * (1.0 / 255.0); for (j = 0; j < bytes - 1; j++) buf[i + j] *= alpha; } } static gdouble pnts_dist (gint x1, gint y1, gint x2, gint y2) { return (sqrt ((x1 - x2 ) * (x1 - x2 ) + (y1 - y2) * (y1 - y2) )); }