📄 xo-paint.c
字号:
#ifdef HAVE_CONFIG_H# include <config.h>#endif#include <math.h>#include <string.h>#include <gtk/gtk.h>#include <libgnomecanvas/libgnomecanvas.h>#include <libart_lgpl/art_vpath_dash.h>#include "xournal.h"#include "xo-callbacks.h"#include "xo-interface.h"#include "xo-support.h"#include "xo-misc.h"#include "xo-paint.h"/************** drawing nice cursors *********/static char cursor_pen_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};static char cursor_eraser_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};static char cursor_eraser_mask[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};void set_cursor_busy(gboolean busy){ GdkCursor *cursor; if (busy) { cursor = gdk_cursor_new(GDK_WATCH); gdk_window_set_cursor(GTK_WIDGET(winMain)->window, cursor); gdk_window_set_cursor(GTK_WIDGET(canvas)->window, cursor); gdk_cursor_unref(cursor); } else { gdk_window_set_cursor(GTK_WIDGET(winMain)->window, NULL); update_cursor(); } gdk_display_sync(gdk_display_get_default());}void update_cursor(void){ GdkPixmap *source, *mask; GdkColor fg = {0, 0, 0, 0}, bg = {0, 65535, 65535, 65535}; if (GTK_WIDGET(canvas)->window == NULL) return; if (ui.cursor!=NULL) { gdk_cursor_unref(ui.cursor); ui.cursor = NULL; } if (ui.cur_item_type == ITEM_MOVESEL_VERT) ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW); else if (ui.cur_item_type == ITEM_MOVESEL) ui.cursor = gdk_cursor_new(GDK_FLEUR); else if (ui.toolno[ui.cur_mapping] == TOOL_PEN) { fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00; fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00; fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00; source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16); ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7); gdk_bitmap_unref(source); } else if (ui.toolno[ui.cur_mapping] == TOOL_ERASER) { source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16); mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16); ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7); gdk_bitmap_unref(source); gdk_bitmap_unref(mask); } else if (ui.toolno[ui.cur_mapping] == TOOL_HIGHLIGHTER) { source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16); mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16); bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00; bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00; bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00; ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7); gdk_bitmap_unref(source); gdk_bitmap_unref(mask); } else if (ui.cur_item_type == ITEM_SELECTRECT) { ui.cursor = gdk_cursor_new(GDK_TCROSS); } else if (ui.toolno[ui.cur_mapping] == TOOL_HAND) { ui.cursor = gdk_cursor_new(GDK_HAND1); } else if (ui.toolno[ui.cur_mapping] == TOOL_TEXT) { ui.cursor = gdk_cursor_new(GDK_XTERM); } gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);}/************** painting strokes *************/#define SUBDIVIDE_MAXDIST 5.0void subdivide_cur_path(null){ int n, pieces, k; double *p; double x0, y0, x1, y1; for (n=0, p=ui.cur_path.coords; n<ui.cur_path.num_points-1; n++, p+=2) { pieces = (int)floor(hypot(p[2]-p[0], p[3]-p[1])/SUBDIVIDE_MAXDIST); if (pieces>1) { x0 = p[0]; y0 = p[1]; x1 = p[2]; y1 = p[3]; realloc_cur_path(ui.cur_path.num_points+pieces-1); g_memmove(ui.cur_path.coords+2*(n+pieces), ui.cur_path.coords+2*(n+1), 2*(ui.cur_path.num_points-n-1)*sizeof(double)); p = ui.cur_path.coords+2*n; ui.cur_path.num_points += pieces-1; n += (pieces-1); for (k=1; k<pieces; k++) { p+=2; p[0] = x0 + k*(x1-x0)/pieces; p[1] = y0 + k*(y1-y0)/pieces; } } }}void create_new_stroke(GdkEvent *event){ ui.cur_item_type = ITEM_STROKE; ui.cur_item = g_new(struct Item, 1); ui.cur_item->type = ITEM_STROKE; g_memmove(&(ui.cur_item->brush), ui.cur_brush, sizeof(struct Brush)); ui.cur_item->path = &ui.cur_path; realloc_cur_path(2); ui.cur_path.num_points = 1; get_pointer_coords(event, ui.cur_path.coords); if (ui.ruler[ui.cur_mapping]) ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group, gnome_canvas_line_get_type(), "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, "fill-color-rgba", ui.cur_item->brush.color_rgba, "width-units", ui.cur_item->brush.thickness, NULL); else ui.cur_item->canvas_item = gnome_canvas_item_new( ui.cur_layer->group, gnome_canvas_group_get_type(), NULL);}void continue_stroke(GdkEvent *event){ GnomeCanvasPoints seg; double *pt; if (ui.ruler[ui.cur_mapping]) { pt = ui.cur_path.coords; } else { realloc_cur_path(ui.cur_path.num_points+1); pt = ui.cur_path.coords + 2*(ui.cur_path.num_points-1); } get_pointer_coords(event, pt+2); if (ui.ruler[ui.cur_mapping]) ui.cur_path.num_points = 2; else { if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom) return; // not a meaningful motion ui.cur_path.num_points++; } seg.coords = pt; seg.num_points = 2; seg.ref_count = 1; /* note: we're using a piece of the cur_path array. This is ok because upon creation the line just copies the contents of the GnomeCanvasPoints into an internal structure */ if (ui.ruler[ui.cur_mapping]) gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL); else gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item, gnome_canvas_line_get_type(), "points", &seg, "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, "fill-color-rgba", ui.cur_item->brush.color_rgba, "width-units", ui.cur_item->brush.thickness, NULL);}void finalize_stroke(void){ if (ui.cur_path.num_points == 1) { // GnomeCanvas doesn't like num_points=1 ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1; ui.cur_path.coords[3] = ui.cur_path.coords[1]; ui.cur_path.num_points = 2; } subdivide_cur_path(); // split the segment so eraser will work ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points); g_memmove(ui.cur_item->path->coords, ui.cur_path.coords, 2*ui.cur_path.num_points*sizeof(double)); update_item_bbox(ui.cur_item); ui.cur_path.num_points = 0; // destroy the entire group of temporary line segments gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item)); // make a new line item to replace it make_canvas_item_one(ui.cur_layer->group, ui.cur_item); // add undo information prepare_new_undo(); undo->type = ITEM_STROKE; undo->item = ui.cur_item; undo->layer = ui.cur_layer; // store the item on top of the layer stack ui.cur_layer->items = g_list_append(ui.cur_layer->items, ui.cur_item); ui.cur_layer->nitems++; ui.cur_item = NULL; ui.cur_item_type = ITEM_NONE;}/************** eraser tool *************/void erase_stroke_portions(struct Item *item, double x, double y, double radius, gboolean whole_strokes, struct UndoErasureData *erasure){ int i; double *pt; struct Item *newhead, *newtail; gboolean need_recalc = FALSE; for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) { if (hypot(pt[0]-x, pt[1]-y) <= radius) { // found an intersection // FIXME: need to test if line SEGMENT hits the circle // hide the canvas item, and create erasure data if needed if (erasure == NULL) { item->type = ITEM_TEMP_STROKE; gnome_canvas_item_hide(item->canvas_item); /* we'll use this hidden item as an insertion point later */ erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData)); item->erasure = erasure; erasure->item = item; erasure->npos = g_list_index(ui.cur_layer->items, item); erasure->nrepl = 0; erasure->replacement_items = NULL; } // split the stroke newhead = newtail = NULL; if (!whole_strokes) { if (i>=2) { newhead = (struct Item *)g_malloc(sizeof(struct Item)); newhead->type = ITEM_STROKE; g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush)); newhead->path = gnome_canvas_points_new(i); g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double)); } while (++i < item->path->num_points) { pt+=2; if (hypot(pt[0]-x, pt[1]-y) > radius) break; } if (i<item->path->num_points-1) { newtail = (struct Item *)g_malloc(sizeof(struct Item)); newtail->type = ITEM_STROKE; g_memmove(&newtail->brush, &item->brush, sizeof(struct Brush)); newtail->path = gnome_canvas_points_new(item->path->num_points-i); g_memmove(newtail->path->coords, item->path->coords+2*i, 2*(item->path->num_points-i)*sizeof(double)); newtail->canvas_item = NULL; } } if (item->type == ITEM_STROKE) { // it's inside an erasure list - we destroy it gnome_canvas_points_free(item->path); if (item->canvas_item != NULL) gtk_object_destroy(GTK_OBJECT(item->canvas_item)); erasure->nrepl--; erasure->replacement_items = g_list_remove(erasure->replacement_items, item); g_free(item); } // add the new head if (newhead != NULL) { update_item_bbox(newhead); make_canvas_item_one(ui.cur_layer->group, newhead); lower_canvas_item_to(ui.cur_layer->group, newhead->canvas_item, erasure->item->canvas_item); erasure->replacement_items = g_list_prepend(erasure->replacement_items, newhead); erasure->nrepl++; // prepending ensures it won't get processed twice } // recurse into the new tail need_recalc = (newtail!=NULL); if (newtail == NULL) break; item = newtail; erasure->replacement_items = g_list_prepend(erasure->replacement_items, newtail); erasure->nrepl++; i=0; pt=item->path->coords; } } // add the tail if needed if (!need_recalc) return; update_item_bbox(item); make_canvas_item_one(ui.cur_layer->group, item); lower_canvas_item_to(ui.cur_layer->group, item->canvas_item, erasure->item->canvas_item);}void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes){ struct Item *item, *repl; GList *itemlist, *repllist; double pos[2]; struct BBox eraserbox; get_pointer_coords(event, pos); eraserbox.left = pos[0]-radius; eraserbox.right = pos[0]+radius; eraserbox.top = pos[1]-radius; eraserbox.bottom = pos[1]+radius; for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) { item = (struct Item *)itemlist->data; if (item->type == ITEM_STROKE) { if (!have_intersect(&(item->bbox), &eraserbox)) continue; erase_stroke_portions(item, pos[0], pos[1], radius, whole_strokes, NULL); } else if (item->type == ITEM_TEMP_STROKE) { repllist = item->erasure->replacement_items; while (repllist!=NULL) { repl = (struct Item *)repllist->data; // we may delete the item soon! so advance now in the list repllist = repllist->next; if (have_intersect(&(repl->bbox), &eraserbox)) erase_stroke_portions(repl, pos[0], pos[1], radius, whole_strokes, item->erasure); } } }}void finalize_erasure(void){ GList *itemlist, *partlist; struct Item *item, *part; prepare_new_undo(); undo->type = ITEM_ERASURE; undo->layer = ui.cur_layer; undo->erasurelist = NULL; itemlist = ui.cur_layer->items; while (itemlist!=NULL) { item = (struct Item *)itemlist->data; itemlist = itemlist->next; if (item->type != ITEM_TEMP_STROKE) continue; item->type = ITEM_STROKE; ui.cur_layer->items = g_list_remove(ui.cur_layer->items, item); // the item has an invisible canvas item, which used to act as anchor if (item->canvas_item!=NULL) { gtk_object_destroy(GTK_OBJECT(item->canvas_item)); item->canvas_item = NULL; } undo->erasurelist = g_list_append(undo->erasurelist, item->erasure); // add the new strokes into the current layer for (partlist = item->erasure->replacement_items; partlist!=NULL; partlist = partlist->next) ui.cur_layer->items = g_list_insert_before( ui.cur_layer->items, itemlist, partlist->data); ui.cur_layer->nitems += item->erasure->nrepl-1; } ui.cur_item = NULL; ui.cur_item_type = ITEM_NONE; /* NOTE: the list of erasures goes in the depth order of the layer; this guarantees that, upon undo, the erasure->npos fields give the correct position where each item should be reinserted as the list is traversed in the forward direction */}/************ selection tools ***********/void make_dashed(GnomeCanvasItem *item){ double dashlen[2]; ArtVpathDash dash; dash.n_dash = 2; dash.offset = 3.0; dash.dash = dashlen; dashlen[0] = dashlen[1] = 6.0; gnome_canvas_item_set(item, "dash", &dash, NULL);}void start_selectrect(GdkEvent *event){ double pt[2];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -