Code

Set up toolbox so that paint bucket defaults can be reset
[inkscape.git] / src / flood-context.cpp
1 #define __SP_FLOOD_CONTEXT_C__
3 /*
4 * Flood fill drawing context
5 *
6 * Author:
7 *   Lauris Kaplinski <lauris@kaplinski.com>
8 *   bulia byak <buliabyak@users.sf.net>
9 *   John Bintz <jcoswell@coswellproductions.org>
10 *
11 * Copyright (C) 2006      Johan Engelen <johan@shouraizou.nl>
12 * Copyright (C) 2000-2005 authors
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #include "config.h"
20 #include <gdk/gdkkeysyms.h>
21 #include <queue>
22 #include <deque>
24 #include "macros.h"
25 #include "display/sp-canvas.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "sp-object.h"
29 #include "sp-rect.h"
30 #include "selection.h"
31 #include "desktop-handles.h"
32 #include "desktop.h"
33 #include "desktop-style.h"
34 #include "message-stack.h"
35 #include "message-context.h"
36 #include "pixmaps/cursor-paintbucket.xpm"
37 #include "flood-context.h"
38 #include "sp-metrics.h"
39 #include <glibmm/i18n.h>
40 #include "object-edit.h"
41 #include "xml/repr.h"
42 #include "xml/node-event-vector.h"
43 #include "prefs-utils.h"
44 #include "context-fns.h"
45 #include "rubberband.h"
47 #include "display/nr-arena-item.h"
48 #include "display/nr-arena.h"
49 #include "display/nr-arena-image.h"
50 #include "display/canvas-arena.h"
51 #include "libnr/nr-pixops.h"
52 #include "libnr/nr-matrix-translate-ops.h"
53 #include "libnr/nr-scale-ops.h"
54 #include "libnr/nr-scale-translate-ops.h"
55 #include "libnr/nr-translate-matrix-ops.h"
56 #include "libnr/nr-translate-scale-ops.h"
57 #include "libnr/nr-matrix-ops.h"
58 #include "sp-item.h"
59 #include "sp-root.h"
60 #include "sp-defs.h"
61 #include "sp-path.h"
62 #include "splivarot.h"
63 #include "livarot/Path.h"
64 #include "livarot/Shape.h"
65 #include "libnr/n-art-bpath.h"
66 #include "svg/svg.h"
67 #include "color.h"
69 #include "trace/trace.h"
70 #include "trace/potrace/inkscape-potrace.h"
72 static void sp_flood_context_class_init(SPFloodContextClass *klass);
73 static void sp_flood_context_init(SPFloodContext *flood_context);
74 static void sp_flood_context_dispose(GObject *object);
76 static void sp_flood_context_setup(SPEventContext *ec);
78 static gint sp_flood_context_root_handler(SPEventContext *event_context, GdkEvent *event);
79 static gint sp_flood_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
81 static void sp_flood_finish(SPFloodContext *rc);
83 static SPEventContextClass *parent_class;
86 GtkType sp_flood_context_get_type()
87 {
88     static GType type = 0;
89     if (!type) {
90         GTypeInfo info = {
91             sizeof(SPFloodContextClass),
92             NULL, NULL,
93             (GClassInitFunc) sp_flood_context_class_init,
94             NULL, NULL,
95             sizeof(SPFloodContext),
96             4,
97             (GInstanceInitFunc) sp_flood_context_init,
98             NULL,    /* value_table */
99         };
100         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPFloodContext", &info, (GTypeFlags) 0);
101     }
102     return type;
105 static void sp_flood_context_class_init(SPFloodContextClass *klass)
107     GObjectClass *object_class = (GObjectClass *) klass;
108     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
110     parent_class = (SPEventContextClass *) g_type_class_peek_parent(klass);
112     object_class->dispose = sp_flood_context_dispose;
114     event_context_class->setup = sp_flood_context_setup;
115     event_context_class->root_handler  = sp_flood_context_root_handler;
116     event_context_class->item_handler  = sp_flood_context_item_handler;
119 static void sp_flood_context_init(SPFloodContext *flood_context)
121     SPEventContext *event_context = SP_EVENT_CONTEXT(flood_context);
123     event_context->cursor_shape = cursor_paintbucket_xpm;
124     event_context->hot_x = 11;
125     event_context->hot_y = 30;
126     event_context->xp = 0;
127     event_context->yp = 0;
128     event_context->tolerance = 4;
129     event_context->within_tolerance = false;
130     event_context->item_to_select = NULL;
132     event_context->shape_repr = NULL;
133     event_context->shape_knot_holder = NULL;
135     flood_context->item = NULL;
137     new (&flood_context->sel_changed_connection) sigc::connection();
140 static void sp_flood_context_dispose(GObject *object)
142     SPFloodContext *rc = SP_FLOOD_CONTEXT(object);
143     SPEventContext *ec = SP_EVENT_CONTEXT(object);
145     rc->sel_changed_connection.disconnect();
146     rc->sel_changed_connection.~connection();
148     /* fixme: This is necessary because we do not grab */
149     if (rc->item) {
150         sp_flood_finish(rc);
151     }
153     if (ec->shape_repr) { // remove old listener
154         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
155         Inkscape::GC::release(ec->shape_repr);
156         ec->shape_repr = 0;
157     }
159     if (rc->_message_context) {
160         delete rc->_message_context;
161     }
163     G_OBJECT_CLASS(parent_class)->dispose(object);
166 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
167     NULL, /* child_added */
168     NULL, /* child_removed */
169     ec_shape_event_attr_changed,
170     NULL, /* content_changed */
171     NULL  /* order_changed */
172 };
174 /**
175 \brief  Callback that processes the "changed" signal on the selection;
176 destroys old and creates new knotholder
177 */
178 void sp_flood_context_selection_changed(Inkscape::Selection *selection, gpointer data)
180     SPFloodContext *rc = SP_FLOOD_CONTEXT(data);
181     SPEventContext *ec = SP_EVENT_CONTEXT(rc);
183     if (ec->shape_repr) { // remove old listener
184         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
185         Inkscape::GC::release(ec->shape_repr);
186         ec->shape_repr = 0;
187     }
189     SPItem *item = selection->singleItem();
190     if (item) {
191         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(item);
192         if (shape_repr) {
193             ec->shape_repr = shape_repr;
194             Inkscape::GC::anchor(shape_repr);
195             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
196         }
197     }
200 static void sp_flood_context_setup(SPEventContext *ec)
202     SPFloodContext *rc = SP_FLOOD_CONTEXT(ec);
204     if (((SPEventContextClass *) parent_class)->setup) {
205         ((SPEventContextClass *) parent_class)->setup(ec);
206     }
208     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
209     if (item) {
210         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(item);
211         if (shape_repr) {
212             ec->shape_repr = shape_repr;
213             Inkscape::GC::anchor(shape_repr);
214             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
215         }
216     }
218     rc->sel_changed_connection.disconnect();
219     rc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(
220         sigc::bind(sigc::ptr_fun(&sp_flood_context_selection_changed), (gpointer)rc)
221     );
223     rc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
226 static void
227 merge_pixel_with_background (unsigned char *orig, unsigned char *bg,
228            unsigned char *base)
230     for (int i = 0; i < 3; i++) {
231         base[i] = (255 * (255 - bg[3])) / 255 + (bg[i] * bg[3]) / 255;
232         base[i] = (base[i] * (255 - orig[3])) / 255 + (orig[i] * orig[3]) / 255;
233     }
234     base[3] = 255;
237 inline unsigned char * get_pixel(guchar *px, int x, int y, int width) {
238     return px + (x + y * width) * 4;
241 GList * flood_channels_dropdown_items_list() {
242     GList *glist = NULL;
244     glist = g_list_append (glist, _("Visible Colors"));
245     glist = g_list_append (glist, _("Red"));
246     glist = g_list_append (glist, _("Green"));
247     glist = g_list_append (glist, _("Blue"));
248     glist = g_list_append (glist, _("Hue"));
249     glist = g_list_append (glist, _("Saturation"));
250     glist = g_list_append (glist, _("Lightness"));
251     glist = g_list_append (glist, _("Alpha"));
253     return glist;
256 static bool compare_pixels(unsigned char *check, unsigned char *orig, unsigned char *dtc, int threshold, PaintBucketChannels method) {
257     int diff = 0;
258     float hsl_check[3], hsl_orig[3];
259     
260     if ((method == FLOOD_CHANNELS_H) ||
261         (method == FLOOD_CHANNELS_S) ||
262         (method == FLOOD_CHANNELS_L)) {
263         sp_color_rgb_to_hsl_floatv(hsl_check, check[0] / 255.0, check[1] / 255.0, check[2] / 255.0);
264         sp_color_rgb_to_hsl_floatv(hsl_orig, orig[0] / 255.0, orig[1] / 255.0, orig[2] / 255.0);
265     }
266     
267     switch (method) {
268         case FLOOD_CHANNELS_ALPHA:
269             return ((int)abs(check[3] - orig[3]) <= threshold);
270         case FLOOD_CHANNELS_R:
271             return ((int)abs(check[0] - orig[0]) <= threshold);
272         case FLOOD_CHANNELS_G:
273             return ((int)abs(check[1] - orig[1]) <= threshold);
274         case FLOOD_CHANNELS_B:
275             return ((int)abs(check[2] - orig[2]) <= threshold);
276         case FLOOD_CHANNELS_RGB:
277             unsigned char merged_orig[4];
278             unsigned char merged_check[4];
279             
280             merge_pixel_with_background(orig, dtc, merged_orig);
281             merge_pixel_with_background(check, dtc, merged_check);
282             
283             for (int i = 0; i < 3; i++) {
284               diff += (int)abs(merged_check[i] - merged_orig[i]);
285             }
286             return ((diff / 3) <= ((threshold * 3) / 4));
287         
288         case FLOOD_CHANNELS_H:
289             return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold);
290         case FLOOD_CHANNELS_S:
291             return ((int)(fabs(hsl_check[1] - hsl_orig[1]) * 100.0) <= threshold);
292         case FLOOD_CHANNELS_L:
293             return ((int)(fabs(hsl_check[2] - hsl_orig[2]) * 100.0) <= threshold);
294     }
295     
296     return false;
299 static bool try_add_to_queue(std::deque<NR::Point> *fill_queue, guchar *px, guchar *trace_px, unsigned char *orig, unsigned char *dtc, int x, int y, int width, int threshold, PaintBucketChannels method, bool fill_switch) {
300     unsigned char *t = get_pixel(px, x, y, width);
301     if (compare_pixels(t, orig, dtc, threshold, method)) {
302         unsigned char *trace_t = get_pixel(trace_px, x, y, width);
303         if (trace_t[3] != 255 && trace_t[0] != 255) {
304             if (fill_switch) {
305                 fill_queue->push_back(NR::Point(x, y));
306                 trace_t[0] = 255;
307             }
308         }
309         return false;
310     }
311     return true;
314 static void do_trace(GdkPixbuf *px, SPDesktop *desktop, NR::Matrix transform, bool union_with_selection) {
315     SPDocument *document = sp_desktop_document(desktop);
316     
317     Inkscape::Trace::Potrace::PotraceTracingEngine pte;
318         
319     pte.setTraceType(Inkscape::Trace::Potrace::TRACE_BRIGHTNESS);
320     pte.setInvert(false);
322     Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(px, true);
323     
324     std::vector<Inkscape::Trace::TracingEngineResult> results = pte.trace(pixbuf);
325     
326     Inkscape::XML::Node *layer_repr = SP_GROUP(desktop->currentLayer())->repr;
327     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
329     long totalNodeCount = 0L;
331     double offset = prefs_get_double_attribute("tools.paintbucket", "offset", 0.0);
333     for (unsigned int i=0 ; i<results.size() ; i++) {
334         Inkscape::Trace::TracingEngineResult result = results[i];
335         totalNodeCount += result.getNodeCount();
337         Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
338         /* Set style */
339         sp_desktop_apply_style_tool (desktop, pathRepr, "tools.paintbucket", false);
341         NArtBpath *bpath = sp_svg_read_path(result.getPathData().c_str());
342         Path *path = bpath_to_Path(bpath);
343         g_free(bpath);
345         if (offset != 0) {
346         
347             Shape *path_shape = new Shape();
348         
349             path->ConvertWithBackData(0.03);
350             path->Fill(path_shape, 0);
351             delete path;
352         
353             Shape *expanded_path_shape = new Shape();
354         
355             expanded_path_shape->ConvertToShape(path_shape, fill_nonZero);
356             path_shape->MakeOffset(expanded_path_shape, offset * desktop->current_zoom(), join_round, 4);
357             expanded_path_shape->ConvertToShape(path_shape, fill_positive);
359             Path *expanded_path = new Path();
360         
361             expanded_path->Reset();
362             expanded_path_shape->ConvertToForme(expanded_path);
363             expanded_path->ConvertEvenLines(1.0);
364             expanded_path->Simplify(1.0);
365         
366             delete path_shape;
367             delete expanded_path_shape;
368         
369             gchar *str = expanded_path->svg_dump_path();
370             if (str && *str) {
371                 pathRepr->setAttribute("d", str);
372                 g_free(str);
373             } else {
374                 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Too much inset</b>, the result is empty."));
375                 Inkscape::GC::release(pathRepr);
376                 g_free(str);
377                 return;
378             }
380             delete expanded_path;
382         } else {
383             gchar *str = path->svg_dump_path();
384             delete path;
385             pathRepr->setAttribute("d", str);
386             g_free(str);
387         }
389         layer_repr->addChild(pathRepr, NULL);
391         SPObject *reprobj = document->getObjectByRepr(pathRepr);
392         if (reprobj) {
393             sp_item_write_transform(SP_ITEM(reprobj), pathRepr, transform, NULL);
394             
395             // premultiply the item transform by the accumulated parent transform in the paste layer
396             NR::Matrix local = sp_item_i2doc_affine(SP_GROUP(desktop->currentLayer()));
397             if (!local.test_identity()) {
398                 gchar const *t_str = pathRepr->attribute("transform");
399                 NR::Matrix item_t (NR::identity());
400                 if (t_str)
401                     sp_svg_transform_read(t_str, &item_t);
402                 item_t *= local.inverse();
403                 // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
404                 gchar *affinestr=sp_svg_transform_write(item_t);
405                 pathRepr->setAttribute("transform", affinestr);
406                 g_free(affinestr);
407             }
409             Inkscape::Selection *selection = sp_desktop_selection(desktop);
411             pathRepr->setPosition(-1);
413             if (union_with_selection) {
414                 desktop->messageStack()->flashF(Inkscape::WARNING_MESSAGE, _("Area filled, path with <b>%d</b> nodes created and unioned with selection."), sp_nodes_in_path(SP_PATH(reprobj)));
415                 selection->add(reprobj);
416                 sp_selected_path_union_skip_undo();
417             } else {
418                 desktop->messageStack()->flashF(Inkscape::WARNING_MESSAGE, _("Area filled, path with <b>%d</b> nodes created."), sp_nodes_in_path(SP_PATH(reprobj)));
419                 selection->set(reprobj);
420             }
422         }
424         Inkscape::GC::release(pathRepr);
426     }
429 struct bitmap_coords_info {
430     bool is_left;
431     int x;
432     int y;
433     int y_limit;
434     int width;
435     int threshold;
436     PaintBucketChannels method;
437     unsigned char *dtc;
438     bool top_fill;
439     bool bottom_fill;
440     NR::Rect bbox;
441     NR::Rect screen;
442 };
444 enum ScanlineCheckResult {
445     SCANLINE_CHECK_OK,
446     SCANLINE_CHECK_ABORTED,
447     SCANLINE_CHECK_BOUNDARY
448 };
450 static ScanlineCheckResult perform_bitmap_scanline_check(std::deque<NR::Point> *fill_queue, guchar *px, guchar *trace_px, unsigned char *orig_color, bitmap_coords_info bci) {
451     bool aborted = false;
452     bool reached_screen_boundary = false;
453     bool ok;
454   
455     bool keep_tracing;
456     unsigned char *t, *trace_t;
457   
458     do {
459         ok = false;
460         if (bci.is_left) {
461             keep_tracing = (bci.x >= 0);
462         } else {
463             keep_tracing = (bci.x < bci.width);
464         }
465         
466         if (keep_tracing) {
467             t = get_pixel(px, bci.x, bci.y, bci.width);
468             if (compare_pixels(t, orig_color, bci.dtc, bci.threshold, bci.method)) {
469                 for (int i = 0; i < 4; i++) { t[i] = 255 - t[i]; }
470                 trace_t = get_pixel(trace_px, bci.x, bci.y, bci.width);
471                 trace_t[3] = 255; 
472                 if (bci.y > 0) { 
473                     bci.top_fill = try_add_to_queue(fill_queue, px, trace_px, orig_color, bci.dtc, bci.x, bci.y - 1, bci.width, bci.threshold, bci.method, bci.top_fill);
474                 }
475                 if (bci.y < bci.y_limit) { 
476                     bci.bottom_fill = try_add_to_queue(fill_queue, px, trace_px, orig_color, bci.dtc, bci.x, bci.y + 1, bci.width, bci.threshold, bci.method, bci.bottom_fill);
477                 }
478                 if (bci.is_left) {
479                     bci.x--;
480                 } else {
481                     bci.x++;
482                 }
483                 ok = true;
484             }
485         } else {
486             if (bci.bbox.min()[NR::X] > bci.screen.min()[NR::X]) {
487                 aborted = true; break;
488             } else {
489                 reached_screen_boundary = true;
490             }
491         }
492     } while (ok);
493     
494     if (aborted) { return SCANLINE_CHECK_ABORTED; }
495     if (reached_screen_boundary) { return SCANLINE_CHECK_BOUNDARY; }
496     return SCANLINE_CHECK_OK;
499 static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *event, bool union_with_selection, bool is_point_fill, bool is_touch_fill) {
500     SPDesktop *desktop = event_context->desktop;
501     SPDocument *document = sp_desktop_document(desktop);
503     /* Create new arena */
504     NRArena *arena = NRArena::create();
505     unsigned dkey = sp_item_display_key_new(1);
507     sp_document_ensure_up_to_date (document);
508     
509     SPItem *document_root = SP_ITEM(SP_DOCUMENT_ROOT(document));
510     NR::Maybe<NR::Rect> bbox = document_root->getBounds(NR::identity());
512     if (!bbox) {
513         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Area is not bounded</b>, cannot fill."));
514         return;
515     }
516     
517     double zoom_scale = desktop->current_zoom();
518     double padding = 1.6;
520     NR::Rect screen = desktop->get_display_area();
522     int width = (int)ceil(screen.extent(NR::X) * zoom_scale * padding);
523     int height = (int)ceil(screen.extent(NR::Y) * zoom_scale * padding);
525     NR::Point origin(screen.min()[NR::X],
526                      sp_document_height(document) - screen.extent(NR::Y) - screen.min()[NR::Y]);
527                     
528     origin[NR::X] = origin[NR::X] + (screen.extent(NR::X) * ((1 - padding) / 2));
529     origin[NR::Y] = origin[NR::Y] + (screen.extent(NR::Y) * ((1 - padding) / 2));
530     
531     NR::scale scale(zoom_scale, zoom_scale);
532     NR::Matrix affine = scale * NR::translate(-origin * scale);
533     
534     /* Create ArenaItems and set transform */
535     NRArenaItem *root = sp_item_invoke_show(SP_ITEM(sp_document_root(document)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
536     nr_arena_item_set_transform(NR_ARENA_ITEM(root), affine);
538     NRGC gc(NULL);
539     nr_matrix_set_identity(&gc.transform);
540     
541     NRRectL final_bbox;
542     final_bbox.x0 = 0;
543     final_bbox.y0 = 0;//row;
544     final_bbox.x1 = width;
545     final_bbox.y1 = height;//row + num_rows;
546     
547     nr_arena_item_invoke_update(root, &final_bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
549     guchar *px = g_new(guchar, 4 * width * height);
550     
551     NRPixBlock B;
552     nr_pixblock_setup_extern( &B, NR_PIXBLOCK_MODE_R8G8B8A8N,
553                               final_bbox.x0, final_bbox.y0, final_bbox.x1, final_bbox.y1,
554                               px, 4 * width, FALSE, FALSE );
555     
556     SPNamedView *nv = sp_desktop_namedview(desktop);
557     unsigned long bgcolor = nv->pagecolor;
558     
559     unsigned char dtc[4];
560     dtc[0] = NR_RGBA32_R(bgcolor);
561     dtc[1] = NR_RGBA32_G(bgcolor);
562     dtc[2] = NR_RGBA32_B(bgcolor);
563     dtc[3] = NR_RGBA32_A(bgcolor);
564     
565     for (int fy = 0; fy < height; fy++) {
566         guchar *p = NR_PIXBLOCK_PX(&B) + fy * B.rs;
567         for (int fx = 0; fx < width; fx++) {
568             for (int i = 0; i < 4; i++) { 
569                 *p++ = dtc[i];
570             }
571         }
572     }
574     nr_arena_item_invoke_render(NULL, root, &final_bbox, &B, NR_ARENA_ITEM_RENDER_NO_CACHE );
575     nr_pixblock_release(&B);
576     
577     // Hide items
578     sp_item_invoke_hide(SP_ITEM(sp_document_root(document)), dkey);
579     
580     nr_arena_item_unref(root);
581     nr_object_unref((NRObject *) arena);
582     
583     guchar *trace_px = g_new(guchar, 4 * width * height);
584     memset(trace_px, 0x00, 4 * width * height);
585     
586     std::deque<NR::Point> fill_queue;
587     std::queue<NR::Point> color_queue;
588     
589     std::vector<NR::Point> fill_points;
590     
591     if (is_point_fill) {
592         fill_points.push_back(NR::Point(event->button.x, event->button.y));
593     } else {
594         Inkscape::Rubberband::Rubberband *r = Inkscape::Rubberband::get();
595         fill_points = r->getPoints();
596     }
598     for (unsigned int i = 0; i < fill_points.size(); i++) {
599         NR::Point pw = NR::Point(fill_points[i][NR::X] / zoom_scale, sp_document_height(document) + (fill_points[i][NR::Y] / zoom_scale)) * affine;
600         
601         pw[NR::X] = (int)MIN(width - 1, MAX(0, pw[NR::X]));
602         pw[NR::Y] = (int)MIN(height - 1, MAX(0, pw[NR::Y]));
603         
604         if (is_touch_fill) {
605             if (i == 0) {
606                 color_queue.push(pw);
607             } else {
608                 fill_queue.push_back(pw);
609             }
610         } else {
611             color_queue.push(pw);
612         }
613     }
615     bool aborted = false;
616     int y_limit = height - 1;
618     PaintBucketChannels method = (PaintBucketChannels)prefs_get_int_attribute("tools.paintbucket", "channels", 0);
619     int threshold = prefs_get_int_attribute_limited("tools.paintbucket", "threshold", 1, 0, 100);
621     switch(method) {
622         case FLOOD_CHANNELS_ALPHA:
623         case FLOOD_CHANNELS_RGB:
624         case FLOOD_CHANNELS_R:
625         case FLOOD_CHANNELS_G:
626         case FLOOD_CHANNELS_B:
627             threshold = (255 * threshold) / 100;
628             break;
629         case FLOOD_CHANNELS_H:
630         case FLOOD_CHANNELS_S:
631         case FLOOD_CHANNELS_L:
632             break;
633       }
635     bool reached_screen_boundary = false;
637     bitmap_coords_info bci;
638     
639     bci.y_limit = y_limit;
640     bci.width = width;
641     bci.threshold = threshold;
642     bci.method = method;
643     bci.bbox = *bbox;
644     bci.screen = screen;
645     bci.dtc = dtc;
647     while (!color_queue.empty() && !aborted) {
648         NR::Point color_point = color_queue.front();
649         color_queue.pop();
650         
651         unsigned char *orig_px = get_pixel(px, (int)color_point[NR::X], (int)color_point[NR::Y], width);
652         unsigned char orig_color[4];
653         for (int i = 0; i < 4; i++) { orig_color[i] = orig_px[i]; }
654         
655         unsigned char merged_orig[4];
656     
657         merge_pixel_with_background(orig_color, dtc, merged_orig);
658         
659         unsigned char *trace_t = get_pixel(trace_px, (int)color_point[NR::X], (int)color_point[NR::Y], width);
660         if ((trace_t[0] != 255) && (trace_t[3] != 255)) {
661           fill_queue.push_front(color_point);
662         }
663         
664         while (!fill_queue.empty() && !aborted) {
665             NR::Point cp = fill_queue.front();
666             fill_queue.pop_front();
667             
668             unsigned char *s = get_pixel(px, (int)cp[NR::X], (int)cp[NR::Y], width);
669             
670             // same color at this point
671             if (compare_pixels(s, orig_color, dtc, threshold, method)) {
672                 int x = (int)cp[NR::X];
673                 int y = (int)cp[NR::Y];
674                 
675                 bool top_fill = true;
676                 bool bottom_fill = true;
677                 
678                 if (y > 0) { 
679                     top_fill = try_add_to_queue(&fill_queue, px, trace_px, orig_color, dtc, x, y - 1, width, threshold, method, top_fill);
680                 } else {
681                     if (bbox->min()[NR::Y] > screen.min()[NR::Y]) {
682                         aborted = true; break;
683                     } else {
684                         reached_screen_boundary = true;
685                     }
686                 }
687                 if (y < y_limit) { 
688                     bottom_fill = try_add_to_queue(&fill_queue, px, trace_px, orig_color, dtc, x, y + 1, width, threshold, method, bottom_fill);
689                   } else {
690                       if (bbox->max()[NR::Y] < screen.max()[NR::Y]) {
691                           aborted = true; break;
692                       } else {
693                           reached_screen_boundary = true;
694                       }
695                 }
696                 
697                 bci.is_left = true;
698                 bci.x = x;
699                 bci.y = y;
700                 bci.top_fill = top_fill;
701                 bci.bottom_fill = bottom_fill;
702                 
703                 ScanlineCheckResult result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci);
704                 
705                 switch (result) {
706                     case SCANLINE_CHECK_ABORTED:
707                         aborted = true;
708                         break;
709                     case SCANLINE_CHECK_BOUNDARY:
710                         reached_screen_boundary = true;
711                         break;
712                     default:
713                         break;
714                 }
715                 
716                 bci.is_left = false;
717                 bci.x = x + 1;
718                 bci.y = y;
719                 bci.top_fill = top_fill;
720                 bci.bottom_fill = bottom_fill;
721                 
722                 result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci);
723                 
724                 switch (result) {
725                     case SCANLINE_CHECK_ABORTED:
726                         aborted = true;
727                         break;
728                     case SCANLINE_CHECK_BOUNDARY:
729                         reached_screen_boundary = true;
730                         break;
731                     default:
732                         break;
733                 }
734             }
735         }
736     }
737     
738     g_free(px);
739     
740     if (aborted) {
741         g_free(trace_px);
742         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Area is not bounded</b>, cannot fill."));
743         return;
744     }
745     
746     if (reached_screen_boundary) {
747         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Only the visible part of the bounded area was filled.</b> If you want to fill all of the area, undo, zoom out, and fill again.")); 
748     }
749     
750     GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(trace_px,
751                                       GDK_COLORSPACE_RGB,
752                                       TRUE,
753                                       8, width, height, width * 4,
754                                       (GdkPixbufDestroyNotify)g_free,
755                                       NULL);
757     NR::Matrix inverted_affine = NR::Matrix(affine).inverse();
758     
759     do_trace(pixbuf, desktop, inverted_affine, union_with_selection);
761     g_free(trace_px);
762     
763     sp_document_done(document, SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area"));
766 static gint sp_flood_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
768     gint ret = FALSE;
770     SPDesktop *desktop = event_context->desktop;
772     switch (event->type) {
773     case GDK_BUTTON_PRESS:
774         if (event->button.state & GDK_CONTROL_MASK) {
775             NR::Point const button_w(event->button.x,
776                                     event->button.y);
777             
778             SPItem *item = sp_event_context_find_item (desktop, button_w, TRUE, TRUE);
779             
780             Inkscape::XML::Node *pathRepr = SP_OBJECT_REPR(item);
781             /* Set style */
782             sp_desktop_apply_style_tool (desktop, pathRepr, "tools.paintbucket", false);
783             sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Set style on object"));
784             ret = TRUE;
785         }
786         break;
787     default:
788         break;
789     }
791     if (((SPEventContextClass *) parent_class)->item_handler) {
792         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
793     }
795     return ret;
798 static gint sp_flood_context_root_handler(SPEventContext *event_context, GdkEvent *event)
800     static bool dragging;
801     
802     gint ret = FALSE;
803     SPDesktop *desktop = event_context->desktop;
805     switch (event->type) {
806     case GDK_BUTTON_PRESS:
807         if ( event->button.button == 1 ) {
808             if (!(event->button.state & GDK_CONTROL_MASK)) {
809                 NR::Point const button_w(event->button.x,
810                                         event->button.y);
811     
812                 if (Inkscape::have_viable_layer(desktop, event_context->defaultMessageContext())) {
813                     // save drag origin
814                     event_context->xp = (gint) button_w[NR::X];
815                     event_context->yp = (gint) button_w[NR::Y];
816                     event_context->within_tolerance = true;
817                     
818                     dragging = true;
819                     
820                     NR::Point const p(desktop->w2d(button_w));
821                     Inkscape::Rubberband::get()->setMode(RUBBERBAND_MODE_TOUCHPATH);
822                     Inkscape::Rubberband::get()->start(desktop, p);
823                 }
824             }
825         }
826     case GDK_MOTION_NOTIFY:
827         if ( dragging
828              && ( event->motion.state & GDK_BUTTON1_MASK ) )
829         {
830             if ( event_context->within_tolerance
831                  && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
832                  && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
833                 break; // do not drag if we're within tolerance from origin
834             }
835             
836             event_context->within_tolerance = false;
837             
838             NR::Point const motion_pt(event->motion.x, event->motion.y);
839             NR::Point const p(desktop->w2d(motion_pt));
840             if (Inkscape::Rubberband::get()->is_started()) {
841                 Inkscape::Rubberband::get()->move(p);
842                 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw over</b> areas to add to fill, hold <b>Alt</b> for touch fill"));
843                 gobble_motion_events(GDK_BUTTON1_MASK);
844             }
845         }
846         break;
848     case GDK_BUTTON_RELEASE:
849         if ( event->button.button == 1 ) {
850             Inkscape::Rubberband::Rubberband *r = Inkscape::Rubberband::get();
851             if (r->is_started()) {
852                 // set "busy" cursor
853                 desktop->setWaitingCursor();
855                 if (SP_IS_EVENT_CONTEXT(event_context)) { 
856                     // Since setWaitingCursor runs main loop iterations, we may have already left this tool!
857                     // So check if the tool is valid before doing anything
858                     dragging = false;
860                     bool is_point_fill = event_context->within_tolerance;
861                     bool is_touch_fill = event->button.state & GDK_MOD1_MASK;
862                     
863                     sp_flood_do_flood_fill(event_context, event, event->button.state & GDK_SHIFT_MASK, is_point_fill, is_touch_fill);
864                     
865                     desktop->clearWaitingCursor();
866                     // restore cursor when done; note that it may already be different if e.g. user 
867                     // switched to another tool during interruptible tracing or drawing, in which case do nothing
869                     ret = TRUE;
870                 }
872                 r->stop();
873                 event_context->defaultMessageContext()->clear();
874             }
875         }
876         break;
877     case GDK_KEY_PRESS:
878         switch (get_group0_keyval (&event->key)) {
879         case GDK_Up:
880         case GDK_Down:
881         case GDK_KP_Up:
882         case GDK_KP_Down:
883             // prevent the zoom field from activation
884             if (!MOD__CTRL_ONLY)
885                 ret = TRUE;
886             break;
887         default:
888             break;
889         }
890         break;
891     default:
892         break;
893     }
895     if (!ret) {
896         if (((SPEventContextClass *) parent_class)->root_handler) {
897             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
898         }
899     }
901     return ret;
905 static void sp_flood_finish(SPFloodContext *rc)
907     rc->_message_context->clear();
909     if ( rc->item != NULL ) {
910         SPDesktop * desktop;
912         desktop = SP_EVENT_CONTEXT_DESKTOP(rc);
914         SP_OBJECT(rc->item)->updateRepr();
916         sp_canvas_end_forced_full_redraws(desktop->canvas);
918         sp_desktop_selection(desktop)->set(rc->item);
919         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET,
920                         _("Fill bounded area"));
922         rc->item = NULL;
923     }
926 void flood_channels_set_channels( gint channels )
928     prefs_set_int_attribute("tools.paintbucket", "channels", channels);
931 /*
932   Local Variables:
933   mode:c++
934   c-file-style:"stroustrup"
935   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
936   indent-tabs-mode:nil
937   fill-column:99
938   End:
939 */
940 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :