/* canvas.c Copyright (C) 2004-2007 Mark Tyler and Dmitry Groshev This file is part of mtPaint. mtPaint 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. mtPaint 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 mtPaint in the file COPYING. */ #include #include #include #include #include "global.h" #include "memory.h" #include "png.h" #include "mainwindow.h" #include "otherwindow.h" #include "mygtk.h" #include "inifile.h" #include "canvas.h" #include "quantizer.h" #include "viewer.h" #include "layer.h" #include "polygon.h" #include "wu.h" #include "prefs.h" #include "ani.h" #include "channels.h" #include "toolbar.h" GtkWidget *label_bar[STATUS_ITEMS]; float can_zoom = 1; // Zoom factor 1..MAX_ZOOM int margin_main_x, margin_main_y, // Top left of image from top left of canvas margin_view_x, margin_view_y; int zoom_flag; int marq_status = MARQUEE_NONE, marq_x1 = -1, marq_y1 = -1, marq_x2 = -1, marq_y2 = -1; // Selection marquee int marq_drag_x, marq_drag_y; // Marquee dragging offset int line_status = LINE_NONE, line_x1, line_y1, line_x2, line_y2; // Line tool int poly_status = POLY_NONE; // Polygon selection tool int clone_x, clone_y; // Clone offsets int recent_files; // Current recent files setting gboolean show_paste, // Show contents of clipboard while pasting col_reverse = FALSE, // Painting with right button text_paste = FALSE, // Are we pasting text? canvas_image_centre = TRUE, // Are we centering the image? chequers_optimize = TRUE // Are we optimizing the chequers for speed? ; /// STATUS BAR void update_image_bar() { char txt[64], txt2[16]; toolbar_update_settings(); // Update A/B labels in settings toolbar if ( mem_img_bpp == 1 ) sprintf(txt2, "%i", mem_cols); else sprintf(txt2, "RGB"); snprintf(txt, 50, "%s %i x %i x %s", channames[mem_channel], mem_width, mem_height, txt2); if ( mem_img[CHN_ALPHA] || mem_img[CHN_SEL] || mem_img[CHN_MASK] ) { strcat(txt, " + "); if ( mem_img[CHN_ALPHA] ) strcat(txt, "A"); if ( mem_img[CHN_SEL] ) strcat(txt, "S"); if ( mem_img[CHN_MASK] ) strcat(txt, "M"); } if ( layers_total>0 ) { sprintf(txt2, " (%i/%i)", layer_selected, layers_total); strcat(txt, txt2); } if ( mem_xpm_trans>=0 ) { sprintf(txt2, " (T=%i)", mem_xpm_trans); strcat(txt, txt2); } strcat(txt, " "); gtk_label_set_text( GTK_LABEL(label_bar[STATUS_GEOMETRY]), txt ); } void update_sel_bar() // Update selection stats on status bar { char txt[64] = ""; int x1, y1, w, h; float lang, llen; grad_info *grad = gradient + mem_channel; if (!status_on[STATUS_SELEGEOM]) return; if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON)) { if ( marq_status > MARQUEE_NONE ) { x1 = marq_x1 < marq_x2 ? marq_x1 : marq_x2; y1 = marq_y1 < marq_y2 ? marq_y1 : marq_y2; w = abs(marq_x2 - marq_x1) + 1; h = abs(marq_y2 - marq_y1) + 1; lang = (180.0 / M_PI) * atan2(marq_x2 - marq_x1, marq_y1 - marq_y2); llen = sqrt(w * w + h * h); snprintf(txt, 60, " %i,%i : %i x %i %.1f' %.1f\"", x1, y1, w, h, lang, llen); } else if (tool_type == TOOL_POLYGON) { snprintf(txt, 60, " (%i)%c", poly_points, poly_status != POLY_DONE ? '+' : '\0'); } } else if ((tool_type == TOOL_GRADIENT) && (grad->status != GRAD_NONE)) { w = grad->x2 - grad->x1; h = grad->y2 - grad->y1; lang = (180.0 / M_PI) * atan2(w, -h); llen = sqrt(w * w + h * h); snprintf(txt, 60, " %i,%i : %i x %i %.1f' %.1f\"", grad->x1, grad->y1, w, h, lang, llen); } gtk_label_set_text(GTK_LABEL(label_bar[STATUS_SELEGEOM]), txt); } static void chan_txt_cat(char *txt, int chan, int x, int y) { char txt2[8]; if ( mem_img[chan] ) { snprintf( txt2, 8, "%i", mem_img[chan][x + mem_width*y] ); strcat(txt, txt2); } } void update_xy_bar(int x, int y) { char txt[96]; int pixel; if (status_on[STATUS_CURSORXY]) { snprintf(txt, 60, "%i,%i", x, y); gtk_label_set_text(GTK_LABEL(label_bar[STATUS_CURSORXY]), txt); } if (!status_on[STATUS_PIXELRGB]) return; if ((x >= 0) && (x < mem_width) && (y >= 0) && (y < mem_height)) { pixel = get_pixel_img(x, y); if (mem_img_bpp == 1) snprintf(txt, 60, "[%u] = {%i,%i,%i}", pixel, mem_pal[pixel].red, mem_pal[pixel].green, mem_pal[pixel].blue); else snprintf(txt, 60, "{%i,%i,%i}", INT_2_R(pixel), INT_2_G(pixel), INT_2_B(pixel)); if (mem_img[CHN_ALPHA] || mem_img[CHN_SEL] || mem_img[CHN_MASK]) { strcat(txt, " + {"); chan_txt_cat(txt, CHN_ALPHA, x, y); strcat(txt, ","); chan_txt_cat(txt, CHN_SEL, x, y); strcat(txt, ","); chan_txt_cat(txt, CHN_MASK, x, y); strcat(txt, "}"); } gtk_label_set_text(GTK_LABEL(label_bar[STATUS_PIXELRGB]), txt); } else gtk_label_set_text(GTK_LABEL(label_bar[STATUS_PIXELRGB]), ""); } static void update_undo_bar() { char txt[32]; if (status_on[STATUS_UNDOREDO]) { sprintf(txt, "%i+%i", mem_undo_done, mem_undo_redo); gtk_label_set_text(GTK_LABEL(label_bar[STATUS_UNDOREDO]), txt); } } void init_status_bar() { update_image_bar(); if ( !status_on[STATUS_GEOMETRY] ) gtk_label_set_text( GTK_LABEL(label_bar[STATUS_GEOMETRY]), "" ); if ( status_on[STATUS_CURSORXY] ) gtk_widget_set_usize(label_bar[STATUS_CURSORXY], 90, -2); else { gtk_widget_set_usize(label_bar[STATUS_CURSORXY], 0, -2); gtk_label_set_text( GTK_LABEL(label_bar[STATUS_CURSORXY]), "" ); } if ( !status_on[STATUS_PIXELRGB] ) gtk_label_set_text( GTK_LABEL(label_bar[STATUS_PIXELRGB]), "" ); if ( !status_on[STATUS_SELEGEOM] ) gtk_label_set_text( GTK_LABEL(label_bar[STATUS_SELEGEOM]), "" ); if (status_on[STATUS_UNDOREDO]) { gtk_widget_set_usize(label_bar[STATUS_UNDOREDO], 50, -2); update_undo_bar(); } else { gtk_widget_set_usize(label_bar[STATUS_UNDOREDO], 0, -2); gtk_label_set_text( GTK_LABEL(label_bar[STATUS_UNDOREDO]), "" ); } } void commit_paste( gboolean undo ) { int fx, fy, fw, fh, fx2, fy2; // Screen coords int i, ofs = 0, ua; unsigned char *image, *mask, *alpha = NULL; fx = marq_x1 > 0 ? marq_x1 : 0; fy = marq_y1 > 0 ? marq_y1 : 0; fx2 = marq_x2 < mem_width ? marq_x2 : mem_width - 1; fy2 = marq_y2 < mem_height ? marq_y2 : mem_height - 1; fw = fx2 - fx + 1; fh = fy2 - fy + 1; ua = channel_dis[CHN_ALPHA]; // Ignore clipboard alpha if disabled mask = malloc(fw); if (!mask) return; /* !!! Not enough memory */ if ((mem_channel == CHN_IMAGE) && RGBA_mode && !mem_clip_alpha && !ua && mem_img[CHN_ALPHA]) { alpha = malloc(fw); if (!alpha) return; memset(alpha, channel_col_A[CHN_ALPHA], fw); } ua |= !mem_clip_alpha; if ( undo ) mem_undo_next(UNDO_PASTE); // Do memory stuff for undo /* Offset in memory */ if (marq_x1 < 0) ofs -= marq_x1; if (marq_y1 < 0) ofs -= marq_y1 * mem_clip_w; image = mem_clipboard + ofs * mem_clip_bpp; for (i = 0; i < fh; i++) { row_protected(fx, fy + i, fw, mask); paste_pixels(fx, fy + i, fw, mask, image, ua ? alpha : mem_clip_alpha + ofs, mem_clip_mask ? mem_clip_mask + ofs : NULL, tool_opacity); image += mem_clip_w * mem_clip_bpp; ofs += mem_clip_w; } free(mask); free(alpha); update_menus(); // Update menu undo issues vw_update_area(fx, fy, fw, fh); main_update_area(fx, fy, fw, fh); } void paste_prepare() { poly_status = POLY_NONE; poly_points = 0; if ( tool_type != TOOL_SELECT && tool_type != TOOL_POLYGON ) { clear_perim(); gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(icon_buttons[DEFAULT_TOOL_ICON]), TRUE ); } else { if ( marq_status != MARQUEE_NONE ) paint_marquee(0, marq_x1, marq_y1); } } void iso_trans( GtkMenuItem *menu_item, gpointer user_data, gint item ) { int i; i = mem_isometrics(item); if ( i==0 ) canvas_undo_chores(); else { if ( i==-666 ) alert_box( _("Error"), _("The image is too large to transform."), _("OK"), NULL, NULL ); else memory_errors(i); } } void create_pal_quantized( GtkMenuItem *menu_item, gpointer user_data, gint item ) { int i = 0; unsigned char newpal[3][256]; mem_undo_next(UNDO_PAL); if ( item==1 ) i = dl1quant(mem_img[CHN_IMAGE], mem_width, mem_height, mem_cols, newpal); if ( item==3 ) i = dl3quant(mem_img[CHN_IMAGE], mem_width, mem_height, mem_cols, newpal); if ( item==5 ) i = wu_quant(mem_img[CHN_IMAGE], mem_width, mem_height, mem_cols, newpal); if ( i!=0 ) memory_errors(i); else { for ( i=0; i= MARQUEE_PASTE ) pressed_select_none( NULL, NULL ); // If the user is pasting, lose it! update_menus(); init_pal(); gtk_widget_queue_draw( drawing_canvas ); gtk_widget_queue_draw( drawing_col_prev ); } } void pressed_greyscale( GtkMenuItem *menu_item, gpointer user_data, gint item ) { spot_undo(UNDO_COL); mem_greyscale(item); init_pal(); update_all_views(); gtk_widget_queue_draw( drawing_col_prev ); } void pressed_rotate_image( GtkMenuItem *menu_item, gpointer user_data, gint item ) { if ( mem_image_rot(item) == 0 ) { check_marquee(); canvas_undo_chores(); } else alert_box( _("Error"), _("Not enough memory to rotate image"), _("OK"), NULL, NULL ); } void pressed_rotate_sel( GtkMenuItem *menu_item, gpointer user_data, gint item ) { if ( mem_sel_rot(item) == 0 ) { check_marquee(); gtk_widget_queue_draw( drawing_canvas ); } else alert_box( _("Error"), _("Not enough memory to rotate clipboard"), _("OK"), NULL, NULL ); } int do_rotate_free(GtkWidget *box, gpointer fdata) { GtkWidget *spin = ((GtkBoxChild*)GTK_BOX(box)->children->data)->widget; int j, smooth = 0, gcor = 0; double angle; gtk_spin_button_update(GTK_SPIN_BUTTON(spin)); angle = gtk_spin_button_get_value_as_float(GTK_SPIN_BUTTON(spin)); if (mem_img_bpp == 3) { GtkWidget *gch = ((GtkBoxChild*)GTK_BOX(box)->children->next->data)->widget; GtkWidget *check = ((GtkBoxChild*)GTK_BOX(box)->children->next->next->data)->widget; gcor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gch)); if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))) smooth = 1; } j = mem_rotate_free(angle, smooth, gcor); if (!j) canvas_undo_chores(); else { if (j == -5) alert_box(_("Error"), _("The image is too large for this rotation."), _("OK"), NULL, NULL); else memory_errors(j); } return TRUE; } void pressed_rotate_free( GtkMenuItem *menu_item, gpointer user_data ) { GtkWidget *box, *spin = add_a_spin(45, -360, 360); box = gtk_vbox_new(FALSE, 5); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 0); gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), 2); if (mem_img_bpp == 3) { add_a_toggle(_("Gamma corrected"), box, inifile_get_gboolean("defaultGamma", FALSE)); add_a_toggle(_("Smooth"), box, TRUE); } filter_window(_("Free Rotate"), box, do_rotate_free, NULL, FALSE); } void pressed_clip_mask( GtkMenuItem *menu_item, gpointer user_data, gint item ) { int i; if ( mem_clip_mask == NULL ) { i = mem_clip_mask_init(item ^ 255); if ( i != 0 ) { memory_errors(1); // Not enough memory return; } } mem_clip_mask_set(item); gtk_widget_queue_draw( drawing_canvas ); } void pressed_clip_alphamask() { unsigned char *old_mask = mem_clip_mask; int i, j = mem_clip_w * mem_clip_h, k; if (!mem_clipboard || !mem_clip_alpha) return; mem_clip_mask = mem_clip_alpha; mem_clip_alpha = NULL; if (old_mask) { for (i = 0; i < j; i++) { k = old_mask[i] * mem_clip_mask[i]; mem_clip_mask[i] = (k + (k >> 8) + 1) >> 8; } free(old_mask); } gtk_widget_queue_draw( drawing_canvas ); } void pressed_clip_alpha_scale() { if (!mem_clipboard || (mem_clip_bpp != 3)) return; if (!mem_clip_mask) mem_clip_mask_init(255); if (!mem_clip_mask) return; if (mem_scale_alpha(mem_clipboard, mem_clip_mask, mem_clip_w, mem_clip_h, TRUE)) return; gtk_widget_queue_draw( drawing_canvas ); } void pressed_clip_mask_all() { int i; i = mem_clip_mask_init(0); if ( i != 0 ) { memory_errors(1); // Not enough memory return; } gtk_widget_queue_draw( drawing_canvas ); } void pressed_clip_mask_clear() { if ( mem_clip_mask != NULL ) { mem_clip_mask_clear(); gtk_widget_queue_draw( drawing_canvas ); } } void pressed_flip_image_v( GtkMenuItem *menu_item, gpointer user_data ) { int i; unsigned char *temp; temp = malloc(mem_width * mem_img_bpp); if (!temp) return; /* Not enough memory for temp buffer */ spot_undo(UNDO_XFORM); for (i = 0; i < NUM_CHANNELS; i++) { if (!mem_img[i]) continue; mem_flip_v(mem_img[i], temp, mem_width, mem_height, BPP(i)); } free(temp); update_all_views(); } void pressed_flip_image_h( GtkMenuItem *menu_item, gpointer user_data ) { int i; spot_undo(UNDO_XFORM); for (i = 0; i < NUM_CHANNELS; i++) { if (!mem_img[i]) continue; mem_flip_h(mem_img[i], mem_width, mem_height, BPP(i)); } update_all_views(); } void pressed_flip_sel_v( GtkMenuItem *menu_item, gpointer user_data ) { unsigned char *temp; temp = malloc(mem_clip_w * mem_clip_bpp); if (!temp) return; /* Not enough memory for temp buffer */ mem_flip_v(mem_clipboard, temp, mem_clip_w, mem_clip_h, mem_clip_bpp); if (mem_clip_mask) mem_flip_v(mem_clip_mask, temp, mem_clip_w, mem_clip_h, 1); if (mem_clip_alpha) mem_flip_v(mem_clip_alpha, temp, mem_clip_w, mem_clip_h, 1); gtk_widget_queue_draw( drawing_canvas ); } void pressed_flip_sel_h( GtkMenuItem *menu_item, gpointer user_data ) { mem_flip_h(mem_clipboard, mem_clip_w, mem_clip_h, mem_clip_bpp); if (mem_clip_mask) mem_flip_h(mem_clip_mask, mem_clip_w, mem_clip_h, 1); if (mem_clip_alpha) mem_flip_h(mem_clip_alpha, mem_clip_w, mem_clip_h, 1); gtk_widget_queue_draw( drawing_canvas ); } void paste_init() { marq_status = MARQUEE_PASTE; cursor_corner = -1; update_sel_bar(); update_menus(); gtk_widget_queue_draw( drawing_canvas ); } void pressed_paste( GtkMenuItem *menu_item, gpointer user_data ) { paste_prepare(); marq_x1 = mem_clip_x; marq_y1 = mem_clip_y; marq_x2 = mem_clip_x + mem_clip_w - 1; marq_y2 = mem_clip_y + mem_clip_h - 1; paste_init(); } void pressed_paste_centre( GtkMenuItem *menu_item, gpointer user_data ) { int w, h; GtkAdjustment *hori, *vert; hori = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolledwindow_canvas)); vert = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow_canvas)); canvas_size(&w, &h); if (hori->page_size > w) mem_icx = 0.5; else mem_icx = (hori->value + hori->page_size * 0.5) / w; if (vert->page_size > h) mem_icy = 0.5; else mem_icy = (vert->value + vert->page_size * 0.5) / h; paste_prepare(); align_size(can_zoom); marq_x1 = mem_width * mem_icx - mem_clip_w * 0.5; marq_y1 = mem_height * mem_icy - mem_clip_h * 0.5; marq_x2 = marq_x1 + mem_clip_w - 1; marq_y2 = marq_y1 + mem_clip_h - 1; paste_init(); } void pressed_rectangle( GtkMenuItem *menu_item, gpointer user_data, gint item ) { int x, y, w, h; spot_undo(UNDO_DRAW); if ( tool_type == TOOL_POLYGON ) { if (!item) poly_outline(); else poly_paint(); } else { x = marq_x1 < marq_x2 ? marq_x1 : marq_x2; y = marq_y1 < marq_y2 ? marq_y1 : marq_y2; w = abs(marq_x1 - marq_x2) + 1; h = abs(marq_y1 - marq_y2) + 1; if (item || (2 * tool_size >= w) || (2 * tool_size >= h)) f_rectangle(x, y, w, h); else { f_rectangle(x, y, w, tool_size); f_rectangle(x, y + tool_size, tool_size, h - 2 * tool_size); f_rectangle(x, y + h - tool_size, w, tool_size); f_rectangle(x + w - tool_size, y + tool_size, tool_size, h - 2 * tool_size); } } update_all_views(); } void pressed_ellipse( GtkMenuItem *menu_item, gpointer user_data, gint item ) { spot_undo(UNDO_DRAW); if (!item) o_ellipse( marq_x1, marq_y1, marq_x2, marq_y2, tool_size ); else f_ellipse( marq_x1, marq_y1, marq_x2, marq_y2 ); update_all_views(); } static int copy_clip() { int i, x, y, w, h, bpp, ofs, delta, len; x = marq_x1 < marq_x2 ? marq_x1 : marq_x2; y = marq_y1 < marq_y2 ? marq_y1 : marq_y2; w = abs(marq_x1 - marq_x2) + 1; h = abs(marq_y1 - marq_y2) + 1; bpp = MEM_BPP; free(mem_clipboard); // Lose old clipboard free(mem_clip_alpha); // Lose old clipboard alpha mem_clip_mask_clear(); // Lose old clipboard mask mem_clip_alpha = NULL; if ((mem_channel == CHN_IMAGE) && mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA]) mem_clip_alpha = malloc(w * h); mem_clipboard = malloc(w * h * bpp); text_paste = FALSE; if (!mem_clipboard) { free(mem_clip_alpha); alert_box( _("Error"), _("Not enough memory to create clipboard"), _("OK"), NULL, NULL ); return (FALSE); } mem_clip_bpp = bpp; mem_clip_x = x; mem_clip_y = y; mem_clip_w = w; mem_clip_h = h; /* Current channel */ ofs = (y * mem_width + x) * bpp; delta = 0; len = w * bpp; for (i = 0; i < h; i++) { memcpy(mem_clipboard + delta, mem_img[mem_channel] + ofs, len); ofs += mem_width * bpp; delta += len; } /* Alpha channel */ if (mem_clip_alpha) { ofs = y * mem_width + x; delta = 0; for (i = 0; i < h; i++) { memcpy(mem_clip_alpha + delta, mem_img[CHN_ALPHA] + ofs, w); ofs += mem_width; delta += w; } } return (TRUE); } static void channel_mask() { int i, j, ofs, delta; if (!mem_img[CHN_SEL] || channel_dis[CHN_SEL]) return; if (mem_channel > CHN_ALPHA) return; if (!mem_clip_mask) mem_clip_mask_init(255); if (!mem_clip_mask) return; ofs = mem_clip_y * mem_width + mem_clip_x; delta = 0; for (i = 0; i < mem_clip_h; i++) { for (j = 0; j < mem_clip_w; j++) mem_clip_mask[delta + j] &= mem_img[CHN_SEL][ofs + j]; ofs += mem_width; delta += mem_clip_w; } } static void cut_clip() { int i, j, k, kk, step, to; unsigned char *sel, fix = 255; spot_undo(UNDO_DRAW); step = mem_clip_mask ? 1 : 0; to = tool_opacity; kk = mem_channel <= CHN_ALPHA ? tool_opacity : 255; for (i = 0; i < mem_clip_h; i++) { sel = mem_clip_mask ? mem_clip_mask + i * mem_clip_w : &fix; for (j = 0; j < mem_clip_w; j++ , sel += step) { k = *sel * kk; tool_opacity = (k + (k >> 8) + 1) >> 8; if (!tool_opacity) continue; put_pixel(mem_clip_x + j, mem_clip_y + i); } } tool_opacity = to; } static void trim_clip() { int i, j, offs, offd, maxx, maxy, minx, miny, nw, nh; unsigned char *tmp; minx = MAX_WIDTH; miny = MAX_HEIGHT; maxx = maxy = 0; /* Find max & min values for shrink wrapping */ for (j = 0; j < mem_clip_h; j++) { offs = mem_clip_w * j; for (i = 0; i < mem_clip_w; i++) { if (!mem_clip_mask[offs + i]) continue; if (i < minx) minx = i; if (i > maxx) maxx = i; if (j < miny) miny = j; if (j > maxy) maxy = j; } } /* No live pixels found */ if (minx > maxx) return; nw = maxx - minx + 1; nh = maxy - miny + 1; /* No decrease so no resize either */ if ((nw == mem_clip_w) && (nh == mem_clip_h)) return; /* Pack data to front */ for (j = miny; j <= maxy; j++) { offs = j * mem_clip_w + minx; offd = (j - miny) * nw; memmove(mem_clip_mask + offd, mem_clip_mask + offs, nw); if (mem_clip_alpha) memmove(mem_clip_alpha + offd, mem_clip_alpha + offs, nw); memmove(mem_clipboard + offd * mem_clip_bpp, mem_clipboard + offs * mem_clip_bpp, nw * mem_clip_bpp); } /* Try to realloc memory for smaller clipboard */ tmp = realloc(mem_clipboard, nw * nh * mem_clip_bpp); if (tmp) mem_clipboard = tmp; tmp = realloc(mem_clip_mask, nw * nh); if (tmp) mem_clip_mask = tmp; if (mem_clip_alpha) { tmp = realloc(mem_clip_alpha, nw * nh); if (tmp) mem_clip_alpha = tmp; } mem_clip_w = nw; mem_clip_h = nh; mem_clip_x += minx; mem_clip_y += miny; } void pressed_copy( GtkMenuItem *menu_item, gpointer user_data, gint item ) { if (!item && (tool_type == TOOL_SELECT) && (marq_status >= MARQUEE_PASTE)) { mem_clip_x = marq_x1 < marq_x2 ? marq_x1 : marq_x2; mem_clip_y = marq_y1 < marq_y2 ? marq_y1 : marq_y2; return; } if (!copy_clip()) return; if (tool_type == TOOL_POLYGON) poly_mask(); channel_mask(); if (item) cut_clip(); update_all_views(); update_menus(); } void pressed_lasso( GtkMenuItem *menu_item, gpointer user_data, gint item ) { if (!copy_clip()) return; if (tool_type == TOOL_POLYGON) poly_mask(); else mem_clip_mask_init(255); poly_lasso(); channel_mask(); trim_clip(); if (item) cut_clip(); pressed_paste_centre( NULL, NULL ); } void update_menus() // Update edit/undo menu { int i, j; update_undo_bar(); men_item_state(menu_only_24, mem_img_bpp == 3); men_item_state(menu_not_indexed, (mem_img_bpp == 3) || (mem_channel != CHN_IMAGE)); men_item_state(menu_only_indexed, mem_img_bpp == 1); men_item_state(menu_alphablend, mem_clipboard && (mem_clip_bpp == 3)); if ( marq_status == MARQUEE_NONE ) { men_item_state(menu_need_selection, FALSE); men_item_state(menu_crop, FALSE); men_item_state(menu_lasso, poly_status == POLY_DONE); men_item_state(menu_need_marquee, poly_status == POLY_DONE); } else { men_item_state(menu_need_marquee, TRUE); men_item_state(menu_lasso, marq_status <= MARQUEE_DONE); /* If we are pasting disallow copy/cut/crop */ men_item_state(menu_need_selection, marq_status < MARQUEE_PASTE); /* Only offer the crop option if the user hasn't selected everything */ men_item_state(menu_crop, (marq_status <= MARQUEE_DONE) && ((abs(marq_x1 - marq_x2) < mem_width - 1) || (abs(marq_y1 - marq_y2) < mem_height - 1))); } /* Forbid RGB-to-indexed paste, but allow indexed-to-RGB */ men_item_state(menu_need_clipboard, mem_clipboard && (mem_clip_bpp <= MEM_BPP)); men_item_state(menu_undo, !!mem_undo_done); men_item_state(menu_redo, !!mem_undo_redo); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_chann_x[mem_channel]), TRUE); for (i = j = 0; i < NUM_CHANNELS; i++) // Enable/disable channel enable/disable { if (mem_img[i]) j++; gtk_widget_set_sensitive(menu_chan_dis[i], !!mem_img[i]); } men_item_state(menu_chan_del, j > 1); } void canvas_undo_chores() { int w, h; canvas_size(&w, &h); gtk_widget_set_usize(drawing_canvas, w, h); update_all_views(); // redraw canvas widget update_menus(); init_pal(); gtk_widget_queue_draw(drawing_col_prev); } void check_undo_paste_bpp() { if (marq_status >= MARQUEE_PASTE && (mem_clip_bpp != MEM_BPP)) pressed_select_none( NULL, NULL ); if ( tool_type == TOOL_SMUDGE && mem_img_bpp == 1 ) gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(icon_buttons[DEFAULT_TOOL_ICON]), TRUE ); // User is smudging and undo/redo to an indexed image - reset tool } void main_undo( GtkMenuItem *menu_item, gpointer user_data ) { mem_undo_backward(); check_undo_paste_bpp(); canvas_undo_chores(); } void main_redo( GtkMenuItem *menu_item, gpointer user_data ) { mem_undo_forward(); check_undo_paste_bpp(); canvas_undo_chores(); } /* Save palette to a file */ static int save_pal(char *file_name, ls_settings *settings) { FILE *fp; int i; if ((fp = fopen(file_name, "w")) == NULL) return -1; if (settings->ftype == FT_GPL) // .gpl file { fprintf(fp, "GIMP Palette\nName: mtPaint\nColumns: 16\n#\n"); for (i = 0; i < settings->colors; i++) fprintf(fp, "%3i %3i %3i\tUntitled\n", settings->pal[i].red, settings->pal[i].green, settings->pal[i].blue); } if (settings->ftype == FT_TXT) // .txt file { fprintf(fp, "%i\n", settings->colors); for (i = 0; i < settings->colors; i++) fprintf(fp, "%i,%i,%i\n", settings->pal[i].red, settings->pal[i].green, settings->pal[i].blue); } fclose( fp ); return 0; } int load_pal(char *file_name) // Load palette file { int i; png_color new_mem_pal[256]; i = mem_load_pal( file_name, new_mem_pal ); if ( i < 0 ) return i; // Failure spot_undo(UNDO_PAL); mem_pal_copy( mem_pal, new_mem_pal ); mem_cols = i; update_all_views(); init_pal(); gtk_widget_queue_draw(drawing_col_prev); return 0; } void update_cols() { if (!mem_img[CHN_IMAGE]) return; // Only do this if we have an image mem_pat_update(); /* !!! The order is significant for gradient sample */ update_image_bar(); if ( marq_status >= MARQUEE_PASTE && text_paste ) { render_text( drawing_col_prev ); check_marquee(); gtk_widget_queue_draw( drawing_canvas ); } gtk_widget_queue_draw( drawing_col_prev ); } void init_pal() // Initialise palette after loading { mem_mask_init(); // Reinit RGB masks mem_pal_init(); // Update palette RGB on screen gtk_widget_set_usize( drawing_palette, PALETTE_WIDTH, 4+mem_cols*16 ); update_cols(); gtk_widget_queue_draw( drawing_palette ); } void set_new_filename( char *fname ) { strncpy( mem_filename, fname, 250 ); update_titlebar(); } static int populate_channel(char *filename) { int ftype, res = -1; ftype = detect_image_format(filename); if (ftype < 0) return (-1); /* Silently fail if no file */ /* Don't bother with mismatched formats */ if (file_formats[ftype].flags & (MEM_BPP == 1 ? FF_IDX : FF_RGB)) res = load_image(filename, FS_CHANNEL_LOAD, ftype); /* Successful */ if (res == 1) canvas_undo_chores(); /* Not enough memory available */ else if (res == FILE_MEM_ERROR) memory_errors(1); /* Unspecified error */ else alert_box(_("Error"), _("Invalid channel file."), _("OK"), NULL, NULL); return (res == 1 ? 0 : -1); } int do_a_load( char *fname ) { char mess[512], real_fname[300]; int res, i, ftype; if ((fname[0] != DIR_SEP) #ifdef WIN32 && (fname[1] != ':') #endif ) { getcwd(real_fname, 256); i = strlen(real_fname); real_fname[i] = DIR_SEP; real_fname[i + 1] = 0; strncat(real_fname, fname, 256); } else strncpy(real_fname, fname, 256); ftype = detect_image_format(real_fname); if ((ftype < 0) || (ftype == FT_NONE)) { alert_box(_("Error"), ftype < 0 ? _("Cannot open file") : _("Unsupported file format"), _("OK"), NULL, NULL); return (1); } set_image(FALSE); if (ftype == FT_LAYERS1) res = load_layers(real_fname); else res = load_image(real_fname, FS_PNG_LOAD, ftype); if ( res<=0 ) // Error loading file { if (res == TOO_BIG) { snprintf(mess, 500, _("File is too big, must be <= to width=%i height=%i : %s"), MAX_WIDTH, MAX_HEIGHT, fname); alert_box( _("Error"), mess, _("OK"), NULL, NULL ); } else { alert_box( _("Error"), _("Unable to load file"), _("OK"), NULL, NULL ); } goto fail; } if ( res == FILE_LIB_ERROR ) alert_box( _("Error"), _("The file import library had to terminate due to a problem with the file (possibly corrupt image data or a truncated file). I have managed to load some data as the header seemed fine, but I would suggest you save this image to a new file to ensure this does not happen again."), _("OK"), NULL, NULL ); if ( res == FILE_MEM_ERROR ) memory_errors(1); // Image was too large for OS /* Whether we loaded something or failed to, old image is gone anyway */ register_file(real_fname); if (ftype != FT_LAYERS1) /* A single image */ { set_new_filename(real_fname); if ( layers_total>0 ) layers_notify_changed(); // We loaded an image into the layers, so notify change } else /* A whole bunch of layers */ { // if ( layers_window == NULL ) pressed_layers( NULL, NULL ); if ( !view_showing ) view_show(); // We have just loaded a layers file so display view & layers window if not up } /* To prevent automatic paste following a file load when enabling * "Changing tool commits paste" via preferences */ pressed_select_none(NULL, NULL); reset_tools(); gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(icon_buttons[DEFAULT_TOOL_ICON]), TRUE ); /* Show new image */ update_all_views(); update_image_bar(); /* These 2 are needed to synchronize the scrollbars & image view */ gtk_adjustment_value_changed( gtk_scrolled_window_get_hadjustment( GTK_SCROLLED_WINDOW(scrolledwindow_canvas) ) ); gtk_adjustment_value_changed( gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(scrolledwindow_canvas) ) ); fail: set_image(TRUE); return (res <= 0); } /// FILE SELECTION WINDOW int check_file( char *fname ) // Does file already exist? Ask if OK to overwrite { char mess[512]; if ( valid_file(fname) == 0 ) { snprintf(mess, 500, _("File: %s already exists. Do you want to overwrite it?"), fname); if ( alert_box( _("File Found"), mess, _("NO"), _("YES"), NULL ) != 2 ) return 1; } return 0; } static void change_image_format(GtkMenuItem *menuitem, GtkWidget *box) { static int flags[] = {FF_TRANS, FF_COMPR, FF_SPOT, FF_SPOT, 0}; GList *chain = GTK_BOX(box)->children->next->next; int i, ftype; ftype = (int)gtk_object_get_user_data(GTK_OBJECT(menuitem)); /* Hide/show name/value widget pairs */ for (i = 0; flags[i]; i++) { if (file_formats[ftype].flags & flags[i]) { gtk_widget_show(((GtkBoxChild*)chain->data)->widget); gtk_widget_show(((GtkBoxChild*)chain->next->data)->widget); } else { gtk_widget_hide(((GtkBoxChild*)chain->data)->widget); gtk_widget_hide(((GtkBoxChild*)chain->next->data)->widget); } chain = chain->next->next; } } static void image_widgets(GtkWidget *box, char *name, int mode) { GtkWidget *opt, *menu, *item, *label, *spin; int i, j, k, mask = FF_256; char *ext = strrchr(name, '.'); ext = ext ? ext + 1 : ""; switch (mode) { default: return; case FS_CHANNEL_SAVE: if (mem_channel != CHN_IMAGE) break; case FS_PNG_SAVE: mask = FF_SAVE_MASK; break; case FS_COMPOSITE_SAVE: mask = FF_RGB; } /* Create controls (!!! two widgets per value - used in traversal) */ label = gtk_label_new(_("File Format")); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 5); opt = gtk_option_menu_new(); gtk_box_pack_start(GTK_BOX(box), opt, FALSE, FALSE, 5); label = gtk_label_new(_("Transparency index")); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 5); spin = add_a_spin(mem_xpm_trans, -1, mem_cols - 1); gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 5); label = gtk_label_new(_("JPEG Save Quality (100=High)")); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 5); spin = add_a_spin(mem_jpeg_quality, 0, 100); gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 5); label = gtk_label_new(_("Hotspot at X =")); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 5); spin = add_a_spin(mem_xbm_hot_x, -1, mem_width - 1); gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 5); label = gtk_label_new(_("Y =")); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 5); spin = add_a_spin(mem_xbm_hot_y, -1, mem_height - 1); gtk_box_pack_start(GTK_BOX(box), spin, FALSE, FALSE, 5); gtk_widget_show_all(box); menu = gtk_menu_new(); for (i = j = k = 0; i < NUM_FTYPES; i++) { if (!(file_formats[i].flags & mask)) continue; if (!strncasecmp(ext, file_formats[i].ext, LONGEST_EXT) || (file_formats[i].ext2[0] && !strncasecmp(ext, file_formats[i].ext2, LONGEST_EXT))) j = k; item = gtk_menu_item_new_with_label(file_formats[i].name); gtk_object_set_user_data(GTK_OBJECT(item), (gpointer)i); gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(change_image_format), (gpointer)box); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); k++; } gtk_widget_show_all(menu); gtk_option_menu_set_menu(GTK_OPTION_MENU(opt), menu); gtk_option_menu_set_history(GTK_OPTION_MENU(opt), j); FIX_OPTION_MENU_SIZE(opt); gtk_signal_emit_by_name(GTK_OBJECT(g_list_nth_data( GTK_MENU_SHELL(menu)->children, j)), "activate", (gpointer)box); } static void ftype_widgets(GtkWidget *box, char *name, int mode) { GtkWidget *opt, *menu, *item, *label; int i, j, k, mask; char *ext = strrchr(name, '.'); mask = mode == FS_PALETTE_SAVE ? FF_PALETTE : FF_IMAGE; ext = ext ? ext + 1 : ""; label = gtk_label_new(_("File Format")); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 10); opt = gtk_option_menu_new(); gtk_box_pack_start(GTK_BOX(box), opt, FALSE, FALSE, 10); menu = gtk_menu_new(); for (i = j = k = 0; i < NUM_FTYPES; i++) { if (!(file_formats[i].flags & mask)) continue; if (!strncasecmp(ext, file_formats[i].ext, LONGEST_EXT) || (file_formats[i].ext2[0] && !strncasecmp(ext, file_formats[i].ext2, LONGEST_EXT))) j = k; item = gtk_menu_item_new_with_label(file_formats[i].name); gtk_object_set_user_data(GTK_OBJECT(item), (gpointer)i); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); k++; } gtk_widget_show_all(box); gtk_widget_show_all(menu); gtk_option_menu_set_menu(GTK_OPTION_MENU(opt), menu); gtk_option_menu_set_history(GTK_OPTION_MENU(opt), j); FIX_OPTION_MENU_SIZE(opt); } static GtkWidget *ls_settings_box(char *name, int mode) { GtkWidget *box, *label; box = gtk_hbox_new(FALSE, 0); gtk_object_set_user_data(GTK_OBJECT(box), (gpointer)mode); switch (mode) /* Only save operations need settings */ { case FS_PNG_SAVE: case FS_CHANNEL_SAVE: case FS_COMPOSITE_SAVE: image_widgets(box, name, mode); break; case FS_LAYER_SAVE: /* !!! No selectable layer file format yet */ break; case FS_EXPORT_GIF: /* !!! No selectable formats yet */ label = gtk_label_new(_("Animation delay")); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0); label = add_a_spin(preserved_gif_delay, 1, MAX_DELAY); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 10); break; case FS_EXPORT_UNDO: case FS_EXPORT_UNDO2: case FS_PALETTE_SAVE: ftype_widgets(box, name, mode); break; default: /* Give a hidden empty box */ return (box); } gtk_widget_show(box); return (box); } static int selected_file_type(GtkWidget *box) { GtkWidget *opt; opt = BOX_CHILD(box, 1); opt = gtk_option_menu_get_menu(GTK_OPTION_MENU(opt)); if (!opt) return (FT_NONE); opt = gtk_menu_get_active(GTK_MENU(opt)); return ((int)gtk_object_get_user_data(GTK_OBJECT(opt))); } void init_ls_settings(ls_settings *settings, GtkWidget *box) { int xmode; /* Set defaults */ memset(settings, 0, sizeof(ls_settings)); settings->ftype = FT_NONE; settings->xpm_trans = mem_xpm_trans; settings->jpeg_quality = mem_jpeg_quality; settings->hot_x = mem_xbm_hot_x; settings->hot_y = mem_xbm_hot_y; settings->gif_delay = preserved_gif_delay; /* Read in settings */ if (box) { xmode = (int)gtk_object_get_user_data(GTK_OBJECT(box)); settings->mode = xmode; switch (xmode) { case FS_PNG_SAVE: case FS_CHANNEL_SAVE: case FS_COMPOSITE_SAVE: settings->ftype = selected_file_type(box); settings->xpm_trans = read_spin(BOX_CHILD(box, 3)); settings->jpeg_quality = read_spin(BOX_CHILD(box, 5)); settings->hot_x = read_spin(BOX_CHILD(box, 7)); settings->hot_y = read_spin(BOX_CHILD(box, 9)); break; case FS_LAYER_SAVE: /* Nothing to do yet */ break; case FS_EXPORT_GIF: /* No formats yet */ settings->gif_delay = read_spin(BOX_CHILD(box, 1)); break; case FS_EXPORT_UNDO: case FS_EXPORT_UNDO2: case FS_PALETTE_SAVE: settings->ftype = selected_file_type(box); break; default: /* Use defaults */ break; } } /* Default expansion of xpm_trans */ settings->rgb_trans = settings->xpm_trans < 0 ? -1 : PNG_2_INT(mem_pal[settings->xpm_trans]); } static void store_ls_settings(ls_settings *settings) { guint32 fflags = file_formats[settings->ftype].flags; switch (settings->mode) { case FS_PNG_SAVE: case FS_CHANNEL_SAVE: case FS_COMPOSITE_SAVE: if (fflags & FF_TRANS) mem_xpm_trans = settings->xpm_trans; if (fflags & FF_COMPR) { mem_jpeg_quality = settings->jpeg_quality; inifile_set_gint32("jpegQuality", mem_jpeg_quality); } if (fflags & FF_SPOT) { mem_xbm_hot_x = settings->hot_x; mem_xbm_hot_y = settings->hot_y; } break; case FS_EXPORT_GIF: preserved_gif_delay = settings->gif_delay; break; } } static void fs_save_loc(GdkWindow *window) { int x, y, width, height; gdk_window_get_size(window, &width, &height); gdk_window_get_root_origin(window, &x, &y); inifile_set_gint32("fs_window_x", x); inifile_set_gint32("fs_window_y", y); inifile_set_gint32("fs_window_w", width); inifile_set_gint32("fs_window_h", height); } static gboolean fs_destroy(GtkWidget *fs) { fs_save_loc(fs->window); gtk_window_set_transient_for(GTK_WINDOW(fs), NULL); gtk_widget_destroy(fs); return FALSE; } static gint fs_ok(GtkWidget *fs) { ls_settings settings; GtkWidget *xtra; char fname[256], mess[512], gif_nam[256], gif_nam2[320], *c, *ext, *ext2; int i, j; /* Pick up extra info */ xtra = GTK_WIDGET(gtk_object_get_user_data(GTK_OBJECT(fs))); init_ls_settings(&settings, xtra); /* Needed to show progress in Windows GTK+2 */ gtk_window_set_modal(GTK_WINDOW(fs), FALSE); /* Looks better if no dialog under progressbar */ fs_save_loc(fs->window); /* Save the location */ gtk_widget_hide(fs); /* File extension */ strncpy(fname, gtk_entry_get_text(GTK_ENTRY( GTK_FILE_SELECTION(fs)->selection_entry)), 256); c = strrchr(fname, '.'); while (TRUE) { /* Cut the extension off */ if ((settings.mode == FS_CLIP_FILE) || (settings.mode == FS_EXPORT_UNDO) || (settings.mode == FS_EXPORT_UNDO2)) { if (!c) break; *c = '\0'; } /* Modify the file extension if needed */ else { ext = file_formats[settings.ftype].ext; if (!ext[0]) break; if (c) /* There is an extension */ { /* Same extension? */ if (!strncasecmp(c + 1, ext, 256)) break; /* Alternate extension? */ ext2 = file_formats[settings.ftype].ext2; if (ext2[0] && !strncasecmp(c + 1, ext2, 256)) break; /* Another file type? */ for (i = 0; i < NUM_FTYPES; i++) { if (strncasecmp(c + 1, file_formats[i].ext, 256) && strncasecmp(c + 1, file_formats[i].ext2, 256)) continue; /* Truncate */ *c = '\0'; break; } } i = strlen(fname); j = strlen(ext); if (i + j + 1 > 250) break; /* Too long */ fname[i] = '.'; strncpy(fname + i + 1, ext, j + 1); } gtk_entry_set_text(GTK_ENTRY( GTK_FILE_SELECTION(fs)->selection_entry), fname); break; } /* Get filename the proper way */ gtkncpy(fname, gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)), 250); switch (settings.mode) { case FS_PNG_LOAD: if (do_a_load(fname) == 1) goto redo; break; case FS_PNG_SAVE: if (check_file(fname)) goto redo; store_ls_settings(&settings); // Update data in memory if (gui_save(fname, &settings) < 0) goto redo; if (layers_total > 0) { /* Filename has changed so layers file needs re-saving to be correct */ if (strcmp(fname, mem_filename)) layers_notify_changed(); } set_new_filename(fname); update_image_bar(); // Update transparency info update_all_views(); // Redraw in case transparency changed break; case FS_PALETTE_LOAD: if (load_pal(fname)) { snprintf(mess, 500, _("File: %s invalid - palette not updated"), fname); alert_box( _("Error"), mess, _("OK"), NULL, NULL ); goto redo; } else notify_changed(); break; case FS_PALETTE_SAVE: if (check_file(fname)) goto redo; settings.pal = mem_pal; settings.colors = mem_cols; if (save_pal(fname, &settings) < 0) goto redo_name; break; case FS_CLIP_FILE: if (clipboard_entry) gtk_entry_set_text(GTK_ENTRY(clipboard_entry), fname); break; case FS_BROWSER_PROG: if (entry_handbook[0]) gtk_entry_set_text(GTK_ENTRY(entry_handbook[0]), fname); break; case FS_HANDBOOK_INDEX: if (entry_handbook[1]) gtk_entry_set_text(GTK_ENTRY(entry_handbook[1]), fname); break; case FS_EXPORT_UNDO: case FS_EXPORT_UNDO2: if (export_undo(fname, &settings)) alert_box( _("Error"), _("Unable to export undo images"), _("OK"), NULL, NULL ); break; case FS_EXPORT_ASCII: if (check_file(fname)) goto redo; if (export_ascii(fname)) alert_box( _("Error"), _("Unable to export ASCII file"), _("OK"), NULL, NULL ); break; case FS_LAYER_SAVE: if (check_file(fname)) goto redo; if (save_layers(fname) != 1) goto redo; break; case FS_GIF_EXPLODE: c = strrchr( preserved_gif_filename, DIR_SEP ); if ( c == NULL ) c = preserved_gif_filename; else c++; snprintf(gif_nam, 250, "%s%c%s", fname, DIR_SEP, c); snprintf(mess, 500, "gifsicle -U --explode \"%s\" -o \"%s\"", preserved_gif_filename, gif_nam ); //printf("%s\n", mess); gifsicle(mess); strncat( gif_nam, ".???", 250 ); wild_space_change( gif_nam, gif_nam2, 315 ); snprintf(mess, 500, "mtpaint -g %i %s &", preserved_gif_delay, gif_nam2 ); //printf("%s\n", mess); gifsicle(mess); break; case FS_EXPORT_GIF: if (check_file(fname)) goto redo; store_ls_settings(&settings); // Update data in memory snprintf(gif_nam, 250, "%s", mem_filename); wild_space_change( gif_nam, gif_nam2, 315 ); for (i = strlen(gif_nam2) - 1; (i >= 0) && (gif_nam2[i] != DIR_SEP); i--) { if ((unsigned char)(gif_nam2[i] - '0') <= 9) gif_nam2[i] = '?'; } snprintf(mess, 500, "%s -d %i %s -o \"%s\"", GIFSICLE_CREATE, settings.gif_delay, gif_nam2, fname); //printf("%s\n", mess); gifsicle(mess); #ifndef WIN32 snprintf(mess, 500, "gifview -a \"%s\" &", fname ); gifsicle(mess); //printf("%s\n", mess); #endif break; case FS_CHANNEL_LOAD: if (populate_channel(fname)) goto redo; break; case FS_CHANNEL_SAVE: if (check_file(fname)) goto redo; settings.img[CHN_IMAGE] = mem_img[mem_channel]; settings.width = mem_width; settings.height = mem_height; if (mem_channel == CHN_IMAGE) { settings.pal = mem_pal; settings.bpp = mem_img_bpp; settings.colors = mem_cols; } else { settings.pal = NULL; /* Greyscale one 'll be created */ settings.bpp = 1; settings.colors = 256; settings.xpm_trans = -1; } if (save_image(fname, &settings)) goto redo_name; break; case FS_COMPOSITE_SAVE: if (check_file(fname)) goto redo; if (layer_save_composite(fname, &settings)) goto redo_name; break; } update_menus(); gtk_window_set_transient_for(GTK_WINDOW(fs), NULL); gtk_widget_destroy(fs); return FALSE; redo_name: snprintf(mess, 500, _("Unable to save file: %s"), fname); alert_box( _("Error"), mess, _("OK"), NULL, NULL ); redo: gtk_widget_show(fs); gtk_window_set_modal(GTK_WINDOW(fs), TRUE); return FALSE; } void file_selector(int action_type) { char *title = NULL, txt[300], txt2[300]; GtkWidget *fs, *xtra; #if GTK_MAJOR_VERSION == 1 GtkAccelGroup* ag = gtk_accel_group_new(); #endif switch (action_type) { case FS_PNG_LOAD: title = _("Load Image File"); if (layers_total == 0) { if (check_for_changes() == 1) return; } else if (check_layers_for_changes() == 1) return; break; case FS_PNG_SAVE: title = _("Save Image File"); break; case FS_PALETTE_LOAD: title = _("Load Palette File"); break; case FS_PALETTE_SAVE: title = _("Save Palette File"); break; case FS_CLIP_FILE: title = _("Select Clipboard File"); break; case FS_BROWSER_PROG: title = _("Select Browser Program"); break; case FS_HANDBOOK_INDEX: title = _("Select Handbook Index File"); break; case FS_EXPORT_UNDO: title = _("Export Undo Images"); break; case FS_EXPORT_UNDO2: title = _("Export Undo Images (reversed)"); break; case FS_EXPORT_ASCII: title = _("Export ASCII Art"); break; case FS_LAYER_SAVE: title = _("Save Layer Files"); break; case FS_GIF_EXPLODE: title = _("Import GIF animation - Choose frames directory"); break; case FS_EXPORT_GIF: title = _("Export GIF animation"); break; case FS_CHANNEL_LOAD: title = _("Load Channel"); break; case FS_CHANNEL_SAVE: title = _("Save Channel"); break; case FS_COMPOSITE_SAVE: title = _("Save Composite Image"); break; } fs = gtk_file_selection_new(title); gtk_window_set_modal(GTK_WINDOW(fs), TRUE); gtk_window_set_default_size(GTK_WINDOW(fs), inifile_get_gint32("fs_window_w", 550), inifile_get_gint32("fs_window_h", 500)); gtk_widget_set_uposition(fs, inifile_get_gint32("fs_window_x", 0), inifile_get_gint32("fs_window_y", 0)); if (action_type == FS_GIF_EXPLODE) { gtk_widget_hide(GTK_WIDGET(GTK_FILE_SELECTION(fs)->selection_entry)); gtk_widget_set_sensitive(GTK_WIDGET(GTK_FILE_SELECTION(fs)->file_list), FALSE); // Don't let the user select files } gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button), "clicked", GTK_SIGNAL_FUNC(fs_ok), GTK_OBJECT(fs)); gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION(fs)->cancel_button), "clicked", GTK_SIGNAL_FUNC(fs_destroy), GTK_OBJECT(fs)); gtk_signal_connect_object(GTK_OBJECT(fs), "delete_event", GTK_SIGNAL_FUNC(fs_destroy), GTK_OBJECT(fs)); if ((action_type == FS_PNG_SAVE) && strcmp(mem_filename, _("Untitled"))) strncpy( txt, mem_filename, 256 ); // If we have a filename and saving else if ((action_type == FS_LAYER_SAVE) && strcmp(layers_filename, _("Untitled"))) strncpy(txt, layers_filename, 256); else if (action_type == FS_LAYER_SAVE) { snprintf(txt, 256, "%s%clayers.txt", inifile_get("last_dir", get_home_directory()), DIR_SEP ); } else { snprintf(txt, 256, "%s%c", inifile_get("last_dir", get_home_directory()), DIR_SEP ); // Default } gtkuncpy(txt2, txt, 512); gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), txt2); xtra = ls_settings_box(txt2, action_type); gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->main_vbox), xtra, FALSE, TRUE, 0); gtk_object_set_user_data(GTK_OBJECT(fs), xtra); #if GTK_MAJOR_VERSION == 1 /* No builtin accelerators - add our own */ gtk_widget_add_accelerator(GTK_FILE_SELECTION(fs)->cancel_button, "clicked", ag, GDK_Escape, 0, (GtkAccelFlags)0); gtk_window_add_accel_group(GTK_WINDOW(fs), ag); #endif gtk_widget_show(fs); gtk_window_set_transient_for(GTK_WINDOW(fs), GTK_WINDOW(main_window)); gdk_window_raise(fs->window); // Needed to ensure window is at the top } void align_size( float new_zoom ) // Set new zoom level { GtkAdjustment *hori, *vert; int w, h, nv_h = 0, nv_v = 0; // New positions of scrollbar if (zoom_flag) return; // Needed as we could be called twice per iteration if (new_zoom < MIN_ZOOM) new_zoom = MIN_ZOOM; if (new_zoom > MAX_ZOOM) new_zoom = MAX_ZOOM; if (new_zoom == can_zoom) return; zoom_flag = 1; hori = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolledwindow_canvas)); vert = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow_canvas)); if (mem_ics == 0) { canvas_size(&w, &h); if (hori->page_size > w) mem_icx = 0.5; else mem_icx = (hori->value + hori->page_size * 0.5) / w; if (vert->page_size > h) mem_icy = 0.5; else mem_icy = (vert->value + vert->page_size * 0.5) / h; } mem_ics = 0; can_zoom = new_zoom; canvas_size(&w, &h); if (hori->page_size < w) nv_h = rint(w * mem_icx - hori->page_size * 0.5); if (vert->page_size < h) nv_v = rint(h * mem_icy - vert->page_size * 0.5); hori->value = nv_h; hori->upper = w; vert->value = nv_v; vert->upper = h; #if GTK_MAJOR_VERSION == 1 gtk_adjustment_value_changed(hori); gtk_adjustment_value_changed(vert); #endif gtk_widget_set_usize(drawing_canvas, w, h); update_image_bar(); zoom_flag = 0; vw_focus_view(); // View window position may need updating toolbar_zoom_update(); } /* This tool is seamless: doesn't draw pixels twice if not requested to - WJ */ static void rec_continuous(int nx, int ny, int w, int h) { linedata line1, line2, line3, line4; int ws2 = w >> 1, hs2 = h >> 1; int i, j, i2, j2, *xv; int dx[3] = {-ws2, w - ws2 - 1, -ws2}; int dy[3] = {-hs2, h - hs2 - 1, -hs2}; i = nx < tool_ox; j = ny < tool_oy; /* Redraw starting square only if need to fill in possible gap when * size changes, or to draw stroke gradient in the proper direction */ if (!tablet_working && !(mem_gradient && (gradient[mem_channel].status == GRAD_NONE))) { i2 = tool_ox + dx[i + 1] + 1 - i * 2; j2 = tool_oy + dy[j + 1] + 1 - j * 2; xv = &line3[0]; } else { i2 = tool_ox + dx[i]; j2 = tool_oy + dy[j]; xv = &i2; } if (tool_ox == nx) { line_init(line1, tool_ox + dx[i], j2, tool_ox + dx[i], ny + dy[j + 1]); line_init(line3, tool_ox + dx[i + 1], j2, tool_ox + dx[i + 1], ny + dy[j + 1]); line2[2] = line4[2] = -1; } else { line_init(line2, tool_ox + dx[i], tool_oy + dy[j + 1], nx + dx[i], ny + dy[j + 1]); line_nudge(line2, i2, j2); line_init(line3, tool_ox + dx[i + 1], tool_oy + dy[j], nx + dx[i + 1], ny + dy[j]); line_nudge(line3, i2, j2); line_init(line1, *xv, line3[1], *xv, line2[1]); line_init(line4, nx + dx[i + 1], ny + dy[j], nx + dx[i + 1], ny + dy[j + 1]); } draw_quad(line1, line2, line3, line4); } void update_all_views() // Update whole canvas on all views { if ( view_showing && vw_drawing ) gtk_widget_queue_draw( vw_drawing ); if ( drawing_canvas ) gtk_widget_queue_draw( drawing_canvas ); } void stretch_poly_line(int x, int y) // Clear old temp line, draw next temp line { if ( poly_points>0 && poly_points= MAX_POLY ) poly_conclude(); paint_poly_marquee(); update_sel_bar(); } void tool_action(int event, int x, int y, int button, gdouble pressure) { int minx = -1, miny = -1, xw = -1, yh = -1; int i, j, k, rx, ry, sx, sy, ts2, tr2, res; int ox, oy, off1, off2; int o_size = tool_size, o_flow = tool_flow, o_opac = tool_opacity, n_vs[3]; int px, py, oox, ooy; // Continuous smudge stuff gboolean rmb_tool, first_point = FALSE; /* Does tool draw with color B when right button pressed? */ rmb_tool = (tool_type <= TOOL_SPRAY) || (tool_type == TOOL_FLOOD); if (rmb_tool) tint_mode[2] = button; /* Swap tint +/- */ if ( pen_down == 0 ) { first_point = TRUE; if ((button == 3) && rmb_tool && !tint_mode[0]) { col_reverse = TRUE; mem_swap_cols(); } } else if ( tool_ox == x && tool_oy == y ) return; // Only do something with a new point if ( tablet_working ) { pressure = pressure <= 0.2 ? -1.0 : pressure >= 1.0 ? 0.0 : (pressure - 1.0) * (1.0 / 0.8); n_vs[0] = tool_size; n_vs[1] = tool_flow; n_vs[2] = tool_opacity; for (i = 0; i < 3; i++) { if (!tablet_tool_use[i]) continue; n_vs[i] *= (tablet_tool_factor[i] > 0 ? 1.0 : 0.0) + tablet_tool_factor[i] * pressure; if (n_vs[i] < 1) n_vs[i] = 1; } tool_size = n_vs[0]; tool_flow = n_vs[1]; tool_opacity = n_vs[2]; } ts2 = tool_size >> 1; tr2 = tool_size - ts2 - 1; /* Handle "exceptional" tools */ res = 1; if (tool_type == TOOL_LINE) { if ( button == 1 ) { line_x1 = x; line_y1 = y; if ( line_status == LINE_NONE ) { line_x2 = x; line_y2 = y; } // Draw circle at x, y if ( line_status == LINE_LINE ) { mem_undo_next(UNDO_TOOL); if ( tool_size > 1 ) { int oldmode = mem_undo_opacity; mem_undo_opacity = TRUE; f_circle( line_x1, line_y1, tool_size ); f_circle( line_x2, line_y2, tool_size ); // Draw tool_size thickness line from 1-2 tline( line_x1, line_y1, line_x2, line_y2, tool_size ); mem_undo_opacity = oldmode; } else sline( line_x1, line_y1, line_x2, line_y2 ); minx = (line_x1 < line_x2 ? line_x1 : line_x2) - ts2; miny = (line_y1 < line_y2 ? line_y1 : line_y2) - ts2; xw = abs( line_x2 - line_x1 ) + 1 + tool_size; yh = abs( line_y2 - line_y1 ) + 1 + tool_size; line_x2 = line_x1; line_y2 = line_y1; line_status = LINE_START; } if ( line_status == LINE_NONE ) line_status = LINE_START; } else stop_line(); // Right button pressed so stop line process } else if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON)) { if ( marq_status == MARQUEE_PASTE ) // User wants to drag the paste box { if ( x>=marq_x1 && x<=marq_x2 && y>=marq_y1 && y<=marq_y2 ) { marq_status = MARQUEE_PASTE_DRAG; marq_drag_x = x - marq_x1; marq_drag_y = y - marq_y1; } } if ( marq_status == MARQUEE_PASTE_DRAG && ( button == 1 || button == 13 || button == 2 ) ) { // User wants to drag the paste box ox = marq_x1; oy = marq_y1; paint_marquee(0, x - marq_drag_x, y - marq_drag_y); marq_x1 = x - marq_drag_x; marq_y1 = y - marq_drag_y; marq_x2 = marq_x1 + mem_clip_w - 1; marq_y2 = marq_y1 + mem_clip_h - 1; paint_marquee(1, ox, oy); } if ( (marq_status == MARQUEE_PASTE_DRAG || marq_status == MARQUEE_PASTE ) && (((button == 3) && (event == GDK_BUTTON_PRESS)) || ((button == 13) && (event == GDK_MOTION_NOTIFY)))) { // User wants to commit the paste commit_paste(TRUE); } if ( tool_type == TOOL_SELECT && button == 3 && (marq_status == MARQUEE_DONE ) ) { pressed_select_none(NULL, NULL); set_cursor(); } if ( tool_type == TOOL_SELECT && button == 1 && (marq_status == MARQUEE_NONE || marq_status == MARQUEE_DONE) ) // Starting a selection { if ( marq_status == MARQUEE_DONE ) { paint_marquee(0, marq_x1-mem_width, marq_y1-mem_height); i = close_to(x, y); if (!(i & 1) ^ (marq_x1 > marq_x2)) marq_x1 = marq_x2; if (!(i & 2) ^ (marq_y1 > marq_y2)) marq_y1 = marq_y2; set_cursor(); } else { marq_x1 = x; marq_y1 = y; } marq_x2 = x; marq_y2 = y; marq_status = MARQUEE_SELECTING; paint_marquee(1, marq_x1-mem_width, marq_y1-mem_height); } else { if ( marq_status == MARQUEE_SELECTING ) // Continuing to make a selection { paint_marquee(0, marq_x1-mem_width, marq_y1-mem_height); marq_x2 = x; marq_y2 = y; paint_marquee(1, marq_x1-mem_width, marq_y1-mem_height); } } if ( tool_type == TOOL_POLYGON ) { if ( poly_status == POLY_NONE && marq_status == MARQUEE_NONE ) { // Start doing something if (button == 1) poly_status = POLY_SELECTING; else if (button) poly_status = POLY_DRAGGING; } if ( poly_status == POLY_SELECTING ) { /* Add another point to polygon */ if (button == 1) poly_add_po(x, y); /* Stop adding points */ else if (button == 3) poly_conclude(); } if ( poly_status == POLY_DRAGGING ) { /* Stop forming polygon */ if (event == GDK_BUTTON_RELEASE) poly_conclude(); /* Add another point to polygon */ else poly_add_po(x, y); } } } else /* Some other kind of tool */ { /* If proper button for tool */ if ((button == 1) || ((button == 3) && rmb_tool)) { // Do memory stuff for undo if (tool_type != TOOL_FLOOD) mem_undo_next(UNDO_TOOL); res = 0; } } /* Handle floodfill here, as too irregular a non-continuous tool */ if (!res && (tool_type == TOOL_FLOOD)) { /* Non-masked start point */ if (pixel_protected(x, y) < 255) { j = get_pixel(x, y); k = mem_channel != CHN_IMAGE ? channel_col_A[mem_channel] : mem_img_bpp == 1 ? mem_col_A : PNG_2_INT(mem_col_A24); if (j != k) /* And never start on colour A */ { spot_undo(UNDO_TOOL); flood_fill(x, y, j); update_all_views(); } } /* Undo the color swap if fill failed */ if (!pen_down && col_reverse) { col_reverse = FALSE; mem_swap_cols(); } res = 1; } /* Handle continuous mode */ while (!res && mem_continuous && !first_point) { minx = tool_ox < x ? tool_ox : x; xw = (tool_ox > x ? tool_ox : x) - minx + tool_size; minx -= ts2; miny = tool_oy < y ? tool_oy : y; yh = (tool_oy > y ? tool_oy : y) - miny + tool_size; miny -= ts2; res = 1; if (ts2 ? tool_type == TOOL_SQUARE : tool_type < TOOL_SPRAY) { rec_continuous(x, y, tool_size, tool_size); break; } if (tool_type == TOOL_CIRCLE) { /* Redraw stroke gradient in proper direction */ if (mem_gradient && (gradient[mem_channel].status == GRAD_NONE)) f_circle(tool_ox, tool_oy, tool_size); tline(tool_ox, tool_oy, x, y, tool_size); f_circle(x, y, tool_size); break; } if (tool_type == TOOL_HORIZONTAL) { miny += ts2; yh -= tool_size - 1; rec_continuous(x, y, tool_size, 1); break; } if (tool_type == TOOL_VERTICAL) { minx += ts2; xw -= tool_size - 1; rec_continuous(x, y, 1, tool_size); break; } if (tool_type == TOOL_SLASH) { g_para(x + tr2, y - ts2, x - ts2, y + tr2, tool_ox - x, tool_oy - y); break; } if (tool_type == TOOL_BACKSLASH) { g_para(x - ts2, y - ts2, x + tr2, y + tr2, tool_ox - x, tool_oy - y); break; } if (tool_type == TOOL_SMUDGE) { linedata line; if (button != 1) break; /* Do nothing on right button */ line_init(line, tool_ox, tool_oy, x, y); while (TRUE) { oox = line[0]; ooy = line[1]; if (line_step(line) < 0) break; mem_smudge(oox, ooy, line[0], line[1]); } break; } xw = yh = -1; /* Nothing was done */ res = 0; /* Non-continuous tool */ break; } /* Handle non-continuous mode & tools */ if (!res) { minx = x - ts2; miny = y - ts2; xw = tool_size; yh = tool_size; switch (tool_type) { case TOOL_SQUARE: f_rectangle(minx, miny, xw, yh); break; case TOOL_CIRCLE: f_circle(x, y, tool_size); break; case TOOL_HORIZONTAL: miny = y; yh = 1; sline(x - ts2, y, x + tr2, y); break; case TOOL_VERTICAL: minx = x; xw = 1; sline(x, y - ts2, x, y + tr2); break; case TOOL_SLASH: sline(x + tr2, y - ts2, x - ts2, y + tr2); break; case TOOL_BACKSLASH: sline(x - ts2, y - ts2, x + tr2, y + tr2); break; case TOOL_SPRAY: for (j = 0; j < tool_flow; j++) { rx = x - ts2 + rand() % tool_size; ry = y - ts2 + rand() % tool_size; IF_IN_RANGE(rx, ry) put_pixel(rx, ry); } break; case TOOL_SHUFFLE: for (j = 0; j < tool_flow; j++) { rx = x - ts2 + rand() % tool_size; ry = y - ts2 + rand() % tool_size; sx = x - ts2 + rand() % tool_size; sy = y - ts2 + rand() % tool_size; IF_IN_RANGE(rx, ry) IF_IN_RANGE(sx, sy) { /* !!! Or do something for partial mask too? !!! */ if (pixel_protected(rx, ry) || pixel_protected(sx, sy)) continue; off1 = rx + ry * mem_width; off2 = sx + sy * mem_width; if ((mem_channel == CHN_IMAGE) && RGBA_mode && mem_img[CHN_ALPHA]) { px = mem_img[CHN_ALPHA][off1]; py = mem_img[CHN_ALPHA][off2]; mem_img[CHN_ALPHA][off1] = py; mem_img[CHN_ALPHA][off2] = px; } k = MEM_BPP; off1 *= k; off2 *= k; for (i = 0; i < k; i++) { px = mem_img[mem_channel][off1]; py = mem_img[mem_channel][off2]; mem_img[mem_channel][off1++] = py; mem_img[mem_channel][off2++] = px; } } } break; case TOOL_SMUDGE: if (!first_point) mem_smudge(tool_ox, tool_oy, x, y); break; case TOOL_CLONE: if (first_point || (tool_ox != x) || (tool_oy != y)) mem_clone(x + clone_x, y + clone_y, x, y); break; default: xw = yh = -1; /* Nothing was done */ break; } } if ((xw >= 0) && (yh >= 0)) /* Some drawing action */ { if (xw + minx > mem_width) xw = mem_width - minx; if (yh + miny > mem_height) yh = mem_height - miny; if (minx < 0) xw += minx , minx = 0; if (miny < 0) yh += miny , miny = 0; if ((xw >= 0) && (yh >= 0)) { main_update_area(minx, miny, xw, yh); vw_update_area(minx, miny, xw, yh); } } tool_ox = x; // Remember the coords just used as they are needed in continuous mode tool_oy = y; if ( tablet_working ) { tool_size = o_size; tool_flow = o_flow; tool_opacity = o_opac; } } void check_marquee() // Check marquee boundaries are OK - may be outside limits via arrow keys { int i; if ( marq_status >= MARQUEE_PASTE ) { mtMAX( marq_x1, marq_x1, 1-mem_clip_w ) mtMAX( marq_y1, marq_y1, 1-mem_clip_h ) mtMIN( marq_x1, marq_x1, mem_width-1 ) mtMIN( marq_y1, marq_y1, mem_height-1 ) marq_x2 = marq_x1 + mem_clip_w - 1; marq_y2 = marq_y1 + mem_clip_h - 1; return; } /* Selection mode in operation */ if ((marq_status != MARQUEE_NONE) || (poly_status == POLY_DONE)) { mtMAX( marq_x1, marq_x1, 0 ) mtMAX( marq_y1, marq_y1, 0 ) mtMAX( marq_x2, marq_x2, 0 ) mtMAX( marq_y2, marq_y2, 0 ) mtMIN( marq_x1, marq_x1, mem_width-1 ) mtMIN( marq_y1, marq_y1, mem_height-1 ) mtMIN( marq_x2, marq_x2, mem_width-1 ) mtMIN( marq_y2, marq_y2, mem_height-1 ) } if ( tool_type == TOOL_POLYGON && poly_points > 0 ) { for ( i=0; ivalue; vc_y1 = vert->value; vc_x2 = hori->value + hori->page_size - 1; vc_y2 = vert->value + vert->page_size - 1; } /* Clip area to visible canvas */ static void clip_area( int *rx, int *ry, int *rw, int *rh ) { // !!! This assumes that if there's clipping, then there aren't margins if ( *rx vc_x2 ) *rw = vc_x2 - *rx + 1; if ( *ry + *rh > vc_y2 ) *rh = vc_y2 - *ry + 1; } void update_paste_chunk( int x1, int y1, int x2, int y2 ) { int ux1, uy1, ux2, uy2, w, h; get_visible(); canvas_size(&w, &h); ux1 = x1 > vc_x1 ? x1 : vc_x1; uy1 = y1 > vc_y1 ? y1 : vc_y1; ux2 = x2 < vc_x2 ? x2 : vc_x2; uy2 = y2 < vc_y2 ? y2 : vc_y2; if (ux2 >= w) ux2 = w - 1; if (uy2 >= h) uy2 = h - 1; /* Only repaint if on visible canvas */ if ((ux1 <= ux2) && (uy1 <= uy2)) repaint_paste(ux1, uy1, ux2, uy2); } void paint_poly_marquee() // Paint polygon marquee { int i, last = poly_points; GdkPoint xy[MAX_POLY + 1]; check_marquee(); if ((tool_type != TOOL_POLYGON) || (poly_points < 2)) return; for (i = 0; i < last; i++) { xy[i].x = margin_main_x + rint((poly_mem[i][0] + 0.5) * can_zoom); xy[i].y = margin_main_y + rint((poly_mem[i][1] + 0.5) * can_zoom); } /* Join 1st & last point if finished */ if (poly_status == POLY_DONE) xy[last++] = xy[0]; gdk_draw_lines( drawing_canvas->window, dash_gc, xy, last ); } void paint_marquee(int action, int new_x, int new_y) { unsigned char *rgb; int x1, y1, x2, y2, w, h, new_x2, new_y2, mst, zoom = 1, scale = 1; int i, j, r, g, b, rx, ry, rw, rh, offx, offy; /* !!! This uses the fact that zoom factor is either N or 1/N !!! */ if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom); else scale = rint(can_zoom); new_x2 = new_x + marq_x2 - marq_x1; new_y2 = new_y + marq_y2 - marq_y1; /* Get onscreen coords */ check_marquee(); x1 = (marq_x1 * scale) / zoom; y1 = (marq_y1 * scale) / zoom; x2 = (marq_x2 * scale) / zoom; y2 = (marq_y2 * scale) / zoom; w = abs(x2 - x1) + scale; h = abs(y2 - y1) + scale; if (x2 < x1) x1 = x2; if (y2 < y1) y1 = y2; get_visible(); if (action == 0) /* Clear */ { mst = marq_status; marq_status = 0; /* Redraw inner area if displaying the clipboard */ if (show_paste && (mst >= MARQUEE_PASTE)) { /* Do nothing if not moved anywhere */ if ((new_x == marq_x1) && (new_y == marq_y1)); /* Full redraw if no intersection */ else if ((new_x2 < marq_x1) || (new_x > marq_x2) || (new_y2 < marq_y1) || (new_y > marq_y2)) repaint_canvas(margin_main_x + x1, margin_main_y + y1, w, h); /* Partial redraw */ else { if (new_x != marq_x1) /* Horizontal shift */ { ry = y1; rh = h; if (new_x < marq_x1) /* Move left */ { rx = (new_x2 * scale) / zoom + scale; rw = x1 + w - rx; } else /* Move right */ { rx = x1; rw = (new_x * scale) / zoom - x1; } clip_area(&rx, &ry, &rw, &rh); repaint_canvas(margin_main_x + rx, margin_main_y + ry, rw, rh); } if (new_y != marq_y1) /* Vertical shift */ { rx = x1; rw = w; if (new_y < marq_y1) /* Move up */ { ry = (new_y2 * scale) / zoom + scale; rh = y1 + h - ry; } else /* Move down */ { ry = y1; rh = (new_y * scale) / zoom - y1; } clip_area(&rx, &ry, &rw, &rh); repaint_canvas(margin_main_x + rx, margin_main_y + ry, rw, rh); } } } /* Redraw only borders themselves */ else { repaint_canvas(margin_main_x + x1, margin_main_y + y1 + 1, 1, h - 2); repaint_canvas(margin_main_x + x1 + w - 1, margin_main_y + y1 + 1, 1, h - 2); repaint_canvas(margin_main_x + x1, margin_main_y + y1, w, 1); repaint_canvas(margin_main_x + x1, margin_main_y + y1 + h - 1, w, 1); } marq_status = mst; } /* Draw */ else if ((action == 1) || (action == 11)) { r = 255; g = b = 0; /* Draw in red */ if (marq_status >= MARQUEE_PASTE) { /* Display paste RGB, only if not being called from repaint_canvas */ /* Only do something if there is a change in position */ if (show_paste && (action == 1) && ((new_x != marq_x1) || (new_y != marq_y1))) update_paste_chunk(marq_x1 < 0 ? 0 : x1 + 1, marq_y1 < 0 ? 0 : y1 + 1, x1 + w - 2, y1 + h - 2); r = g = 0; b = 255; /* Draw in blue */ } /* Determine visible area */ rx = x1; ry = y1; rw = w; rh = h; clip_area(&rx, &ry, &rw, &rh); if ((rw < 1) || (rh < 1)) return; offx = (abs(rx - x1) % 6) * 3; offy = (abs(ry - y1) % 6) * 3; /* Create pattern */ j = (rw > rh ? rw : rh) * 3 + 6 * 3; /* 6 pixels for offset */ rgb = malloc(j + 2 * 3); /* 2 extra pixels reserved for loop */ if (!rgb) return; memset(rgb, 255, j); for (i = 0; i < j; i += 6 * 3) { rgb[i + 0] = rgb[i + 3] = rgb[i + 6] = r; rgb[i + 1] = rgb[i + 4] = rgb[i + 7] = g; rgb[i + 2] = rgb[i + 5] = rgb[i + 8] = b; } i = ((mem_width + zoom - 1) * scale) / zoom; j = ((mem_height + zoom - 1) * scale) / zoom; if (rx + rw > i) rw = i - rx; if (ry + rh > j) rh = j - ry; if ((x1 >= vc_x1) && (marq_x1 >= 0) && (marq_x2 >= 0)) gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc, margin_main_x + rx, margin_main_y + ry, 1, rh, GDK_RGB_DITHER_NONE, rgb + offy, 3); if ((x1 + w - 1 <= vc_x2) && (marq_x1 < mem_width) && (marq_x2 < mem_width)) gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc, margin_main_x + rx + rw - 1, margin_main_y + ry, 1, rh, GDK_RGB_DITHER_NONE, rgb + offy, 3); if ((y1 >= vc_y1) && (marq_y1 >= 0) && (marq_y2 >= 0)) gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc, margin_main_x + rx, margin_main_y + ry, rw, 1, GDK_RGB_DITHER_NONE, rgb + offx, 0); if ((y1 + h - 1 <= vc_y2) && (marq_y1 < mem_height) && (marq_y2 < mem_height)) gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc, margin_main_x + rx, margin_main_y + ry + rh - 1, rw, 1, GDK_RGB_DITHER_NONE, rgb + offx, 0); free(rgb); } } int close_to( int x1, int y1 ) // Which corner of selection is coordinate closest to? { return ((x1 + x1 <= marq_x1 + marq_x2 ? 0 : 1) + (y1 + y1 <= marq_y1 + marq_y2 ? 0 : 2)); } static int floor_div(int dd, int dr) { return (dd < 0 ? -((dr - 1 - dd) / dr) : dd / dr); } #define MIN_REDRAW 16 /* Minimum dimension for redraw rectangle */ void trace_line(int mode, int lx1, int ly1, int lx2, int ly2, int vx1, int vy1, int vx2, int vy2) { int j, x, y, tx, ty, aw, ah, ax = 0, ay = 0, cf = 0, zoom = 1, scale = 1; unsigned char rgb[MAX_ZOOM * 3], col[3]; linedata line; /* !!! This uses the fact that zoom factor is either N or 1/N !!! */ if (can_zoom < 1.0) { zoom = rint(1.0 / can_zoom); lx1 = floor_div(lx1, zoom); ly1 = floor_div(ly1, zoom); lx2 = floor_div(lx2, zoom); ly2 = floor_div(ly2, zoom); } else scale = rint(can_zoom); line_init(line, lx1, ly1, lx2, ly2); for (; line[2] >= 0; line_step(line)) { x = (tx = line[0]) * scale; y = (ty = line[1]) * scale; if ((x + scale > vx1) && (y + scale > vy1) && (x <= vx2) && (y <= vy2)) { if (mode != 0) /* Show a line */ { if (mode == 1) /* Drawing */ { j = ((ty & 7) * 8 + (tx & 7)) * 3; col[0] = mem_col_pat24[j + 0]; col[1] = mem_col_pat24[j + 1]; col[2] = mem_col_pat24[j + 2]; } else if (mode == 2) /* Tracking */ { col[0] = col[1] = col[2] = ((line[2] >> 2) & 1) * 255; } else if (mode == 3) /* Gradient */ { col[0] = col[1] = col[2] = ((line[2] >> 2) & 1) * 255; col[1] ^= ((line[2] >> 1) & 1) * 255; } for (j = 0; j < scale * 3; j += 3) { rgb[j + 0] = col[0]; rgb[j + 1] = col[1]; rgb[j + 2] = col[2]; } gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc, margin_main_x + x, margin_main_y + y, scale, scale, GDK_RGB_DITHER_NONE, rgb, 0); continue; } /* Doing a clear */ if (!cf) ax = x , ay = y , cf = 1; // Start a new rectangle } /* Do nothing if no area to clear */ else if ((mode != 0) || !cf) continue; /* Redraw now or wait some more? */ aw = scale + abs(x - ax); ah = scale + abs(y - ay); if ((aw < MIN_REDRAW) && (ah < MIN_REDRAW) && line[2]) continue; /* Commit canvas clear if >16 pixels or final pixel of this line */ repaint_canvas(margin_main_x + (ax < x ? ax : x), margin_main_y + (ay < y ? ay : y), aw, ah); cf = 0; } } void repaint_line(int mode) // Repaint or clear line on canvas { get_visible(); trace_line(mode, line_x1, line_y1, line_x2, line_y2, // !!! This assumes that if there's clipping, then there aren't margins vc_x1, vc_y1, vc_x2, vc_y2); } void repaint_grad(int mode) { grad_info *grad = gradient + mem_channel; int oldgrad = grad->status; if (mode) mode = 3; else grad->status = GRAD_NONE; /* To avoid hitting repaint */ get_visible(); trace_line(mode, grad->x2, grad->y2, grad->x1, grad->y1, vc_x1 - margin_main_x, vc_y1 - margin_main_y, vc_x2 - margin_main_x, vc_y2 - margin_main_y); grad->status = oldgrad; } void refresh_grad(int px, int py, int pw, int ph) { grad_info *grad = gradient + mem_channel; pw += px - 1; ph += py - 1; get_visible(); trace_line(3, grad->x2, grad->y2, grad->x1, grad->y1, (px > vc_x1 ? px : vc_x1) - margin_main_x, (py > vc_y1 ? py : vc_y1) - margin_main_y, (pw < vc_x2 ? pw : vc_x2) - margin_main_x, (ph < vc_y2 ? ph : vc_y2) - margin_main_y); } void men_item_visible( GtkWidget *menu_items[], gboolean state ) { // Show or hide menu items int i = 0; while ( menu_items[i] != NULL ) { if ( state ) gtk_widget_show( menu_items[i] ); else gtk_widget_hide( menu_items[i] ); i++; } } void update_recent_files() // Update the menu items { char txt[64], *t, txt2[600]; int i, count; if ( recent_files == 0 ) men_item_visible( menu_recent, FALSE ); else { for ( i=0; i<=MAX_RECENT; i++ ) // Show or hide items { if ( i <= recent_files ) gtk_widget_show( menu_recent[i] ); else gtk_widget_hide( menu_recent[i] ); } count = 0; for ( i=1; i<=recent_files; i++ ) // Display recent filenames { sprintf( txt, "file%i", i ); t = inifile_get( txt, "." ); if ( strlen(t) < 2 ) gtk_widget_hide( menu_recent[i] ); // Hide if empty else { gtkuncpy(txt2, t, 512); gtk_label_set_text( GTK_LABEL( GTK_MENU_ITEM( menu_recent[i] )->item.bin.child ) , txt2 ); count++; } } if ( count == 0 ) gtk_widget_hide( menu_recent[0] ); // Hide separator if not needed } } void register_file( char *filename ) // Called after successful load/save { char txt[280], *c; int i, f; c = strrchr( filename, DIR_SEP ); if (c) { txt[0] = *c; *c = '\0'; // Strip off filename inifile_set("last_dir", filename); *c = txt[0]; } // Is it already in used file list? If so shift relevant filenames down and put at top. i = 1; f = 0; while ( i1 ) // If file is already most recent, do nothing { while ( i>1 ) { sprintf( txt, "file%i", i-1 ); sprintf( txt+100, "file%i", i ); inifile_set( txt+100, inifile_get( txt, "" ) ); i--; } inifile_set("file1", filename); // Strip off filename } update_recent_files(); } void scroll_wheel( int x, int y, int d ) // Scroll wheel action from mouse { if (d == 1) zoom_in(); else zoom_out(); } void create_default_image() // Create default new image { int nw = inifile_get_gint32("lastnewWidth", DEFAULT_WIDTH ), nh = inifile_get_gint32("lastnewHeight", DEFAULT_HEIGHT ), nc = inifile_get_gint32("lastnewCols", 256 ), nt = inifile_get_gint32("lastnewType", 2 ), bpp = 1; if ( nt == 0 || nt>2 ) bpp = 3; do_new_one( nw, nh, nc, nt, bpp ); }