X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fflood-context.cpp;h=aa6eab7b36cb7390b76ac8cd697a73a5f333378f;hb=ac63672b8bd0ad9539384d4eac6e27875d4d2693;hp=d1c2ad65b7c99e05c0ed63e7bdfa603bbdfe6093;hpb=4ff70c35ab0d7061452009504c921135f06212d6;p=inkscape.git diff --git a/src/flood-context.cpp b/src/flood-context.cpp index d1c2ad65b..aa6eab7b3 100644 --- a/src/flood-context.cpp +++ b/src/flood-context.cpp @@ -48,7 +48,6 @@ #include "display/nr-arena.h" #include "display/nr-arena-image.h" #include "display/canvas-arena.h" -#include "helper/png-write.h" #include "libnr/nr-pixops.h" #include "libnr/nr-matrix-rotate-ops.h" #include "libnr/nr-matrix-translate-ops.h" @@ -61,11 +60,13 @@ #include "sp-item.h" #include "sp-root.h" #include "sp-defs.h" +#include "sp-path.h" #include "splivarot.h" #include "livarot/Path.h" #include "livarot/Shape.h" #include "libnr/n-art-bpath.h" #include "svg/svg.h" +#include "color.h" #include "trace/trace.h" #include "trace/potrace/inkscape-potrace.h" @@ -84,15 +85,6 @@ static void sp_flood_finish(SPFloodContext *rc); static SPEventContextClass *parent_class; -struct SPEBP { - int width, height, sheight; - guchar r, g, b, a; - NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden - guchar *px; - unsigned (*status)(float, void *); - void *data; -}; - GtkType sp_flood_context_get_type() { static GType type = 0; @@ -233,47 +225,99 @@ static void sp_flood_context_setup(SPEventContext *ec) rc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); } -/** -Hide all items which are not listed in list, recursively, skipping groups and defs -*/ -static void -hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey) -{ - if (SP_IS_ITEM(o) - && !SP_IS_DEFS(o) - && !SP_IS_ROOT(o) - && !SP_IS_GROUP(o) - && !g_slist_find(list, o)) - { - sp_item_invoke_hide(SP_ITEM(o), dkey); - } - - // recurse - if (!g_slist_find(list, o)) { - for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { - hide_other_items_recursively(child, list, dkey); - } - } +static void merge_pixel_with_background(unsigned char *orig, unsigned char *bg, unsigned char *base) { + for (int i = 0; i < 3; i++) { + base[i] = (255 * (255 - bg[3])) / 255 + (bg[i] * bg[3]) / 255; + base[i] = (base[i] * (255 - orig[3])) / 255 + (orig[i] * orig[3]) / 255; + } + base[3] = 255; } inline unsigned char * get_pixel(guchar *px, int x, int y, int width) { return px + (x + y * width) * 4; } -static bool compare_pixels(unsigned char *a, unsigned char *b, int tolerance) { - for (int i = 0; i < 4; i++) { - if (abs(a[i] - b[i]) > tolerance) { - return false; - } +enum PaintBucketChannels { + FLOOD_CHANNELS_RGB, + FLOOD_CHANNELS_R, + FLOOD_CHANNELS_G, + FLOOD_CHANNELS_B, + FLOOD_CHANNELS_H, + FLOOD_CHANNELS_S, + FLOOD_CHANNELS_L, + FLOOD_CHANNELS_ALPHA +}; + +GList * flood_channels_dropdown_items_list() { + GList *glist = NULL; + + glist = g_list_append (glist, _("Visible Colors")); + glist = g_list_append (glist, _("Red")); + glist = g_list_append (glist, _("Green")); + glist = g_list_append (glist, _("Blue")); + glist = g_list_append (glist, _("Hue")); + glist = g_list_append (glist, _("Saturation")); + glist = g_list_append (glist, _("Lightness")); + glist = g_list_append (glist, _("Alpha")); + + return glist; +} + +static bool compare_pixels(unsigned char *check, unsigned char *orig, unsigned char *dtc, int threshold, PaintBucketChannels method) { + int diff = 0; + float hsl_check[3], hsl_orig[3]; + + if ((method == FLOOD_CHANNELS_H) || + (method == FLOOD_CHANNELS_S) || + (method == FLOOD_CHANNELS_L)) { + sp_color_rgb_to_hsl_floatv(hsl_check, check[0] / 255.0, check[1] / 255.0, check[2] / 255.0); + sp_color_rgb_to_hsl_floatv(hsl_orig, orig[0] / 255.0, orig[1] / 255.0, orig[2] / 255.0); } - return true; + + switch (method) { + case FLOOD_CHANNELS_ALPHA: + return ((int)abs(check[3] - orig[3]) <= threshold); + case FLOOD_CHANNELS_R: + return ((int)abs(check[0] - orig[0]) <= threshold); + case FLOOD_CHANNELS_G: + return ((int)abs(check[1] - orig[1]) <= threshold); + case FLOOD_CHANNELS_B: + return ((int)abs(check[2] - orig[2]) <= threshold); + case FLOOD_CHANNELS_RGB: + unsigned char merged_orig[4]; + unsigned char merged_check[4]; + + merge_pixel_with_background(orig, dtc, merged_orig); + merge_pixel_with_background(check, dtc, merged_check); + + for (int i = 0; i < 3; i++) { + diff += (int)abs(merged_check[i] - merged_orig[i]); + } + return ((diff / 3) <= ((threshold * 3) / 4)); + + case FLOOD_CHANNELS_H: + return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold); + case FLOOD_CHANNELS_S: + return ((int)(fabs(hsl_check[1] - hsl_orig[1]) * 100.0) <= threshold); + case FLOOD_CHANNELS_L: + return ((int)(fabs(hsl_check[2] - hsl_orig[2]) * 100.0) <= threshold); + } + + return false; } -static void try_add_to_queue(std::queue *fill_queue, guchar *px, unsigned char *orig, int x, int y, int width, int tolerance) { +static bool try_add_to_queue(std::queue *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) { unsigned char *t = get_pixel(px, x, y, width); - if (compare_pixels(t, orig, tolerance)) { - fill_queue->push(NR::Point(x, y)); + if (compare_pixels(t, orig, dtc, threshold, method)) { + unsigned char *trace_t = get_pixel(trace_px, x, y, width); + if (trace_t[3] != 255) { + if (fill_switch) { + fill_queue->push(NR::Point(x, y)); + } + } + return false; } + return true; } static void do_trace(GdkPixbuf *px, SPDesktop *desktop, NR::Matrix transform) { @@ -293,6 +337,8 @@ static void do_trace(GdkPixbuf *px, SPDesktop *desktop, NR::Matrix transform) { long totalNodeCount = 0L; + double offset = prefs_get_double_attribute("tools.paintbucket", "offset", 0.0); + for (unsigned int i=0 ; iConvertWithBackData(0.03); - path->Fill(path_shape, 0); - delete path; + path->ConvertWithBackData(0.03); + path->Fill(path_shape, 0); + delete path; - Shape *expanded_path_shape = new Shape(); + Shape *expanded_path_shape = new Shape(); - expanded_path_shape->ConvertToShape(path_shape, fill_nonZero); - path_shape->MakeOffset(expanded_path_shape, 1.5, join_round, 4); - expanded_path_shape->ConvertToShape(path_shape, fill_positive); + expanded_path_shape->ConvertToShape(path_shape, fill_nonZero); + path_shape->MakeOffset(expanded_path_shape, offset * desktop->current_zoom(), join_round, 4); + expanded_path_shape->ConvertToShape(path_shape, fill_positive); - Path *expanded_path = new Path(); + Path *expanded_path = new Path(); - expanded_path->Reset(); - expanded_path_shape->ConvertToForme(expanded_path); - expanded_path->ConvertEvenLines(1.0); - expanded_path->Simplify(1.0); + expanded_path->Reset(); + expanded_path_shape->ConvertToForme(expanded_path); + expanded_path->ConvertEvenLines(1.0); + expanded_path->Simplify(1.0); - delete path_shape; - delete expanded_path_shape; - - gchar *str = expanded_path->svg_dump_path(); - delete expanded_path; - pathRepr->setAttribute("d", str); - g_free(str); + delete path_shape; + delete expanded_path_shape; + gchar *str = expanded_path->svg_dump_path(); + if (str && *str) { + pathRepr->setAttribute("d", str); + g_free(str); + } else { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Too much inset, the result is empty.")); + Inkscape::GC::release(pathRepr); + g_free(str); + return; + } + + delete expanded_path; + + } else { + gchar *str = path->svg_dump_path(); + delete path; + pathRepr->setAttribute("d", str); + g_free(str); + } + layer_repr->addChild(pathRepr, NULL); SPObject *reprobj = document->getObjectByRepr(pathRepr); if (reprobj) { sp_item_write_transform(SP_ITEM(reprobj), pathRepr, transform, NULL); + + // premultiply the item transform by the accumulated parent transform in the paste layer + NR::Matrix local = sp_item_i2doc_affine(SP_GROUP(desktop->currentLayer())); + if (!local.test_identity()) { + gchar const *t_str = pathRepr->attribute("transform"); + NR::Matrix item_t (NR::identity()); + if (t_str) + sp_svg_transform_read(t_str, &item_t); + item_t *= local.inverse(); + // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform) + gchar *affinestr=sp_svg_transform_write(item_t); + pathRepr->setAttribute("transform", affinestr); + g_free(affinestr); + } + Inkscape::Selection *selection = sp_desktop_selection(desktop); selection->set(reprobj); pathRepr->setPosition(-1); + + desktop->messageStack()->flashF(Inkscape::WARNING_MESSAGE, _("Area filled, path with %d nodes created."), sp_nodes_in_path(SP_PATH(reprobj))); } Inkscape::GC::release(pathRepr); + } } +struct bitmap_coords_info { + bool is_left; + int x; + int y; + int y_limit; + int width; + int threshold; + PaintBucketChannels method; + unsigned char *dtc; + bool top_fill; + bool bottom_fill; + NR::Rect bbox; + NR::Rect screen; +}; + +enum ScanlineCheckResult { + SCANLINE_CHECK_OK, + SCANLINE_CHECK_ABORTED, + SCANLINE_CHECK_BOUNDARY +}; + +static ScanlineCheckResult perform_bitmap_scanline_check(std::queue *fill_queue, guchar *px, guchar *trace_px, unsigned char *orig_color, bitmap_coords_info bci) { + bool aborted = false; + bool reached_screen_boundary = false; + bool ok; + + bool keep_tracing; + unsigned char *t, *trace_t; + + do { + ok = false; + if (bci.is_left) { + keep_tracing = (bci.x >= 0); + } else { + keep_tracing = (bci.x < bci.width); + } + + if (keep_tracing) { + t = get_pixel(px, bci.x, bci.y, bci.width); + if (compare_pixels(t, orig_color, bci.dtc, bci.threshold, bci.method)) { + for (int i = 0; i < 4; i++) { t[i] = 255 - t[i]; } + trace_t = get_pixel(trace_px, bci.x, bci.y, bci.width); + trace_t[3] = 255; + if (bci.y > 0) { + 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); + } + if (bci.y < bci.y_limit) { + 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); + } + if (bci.is_left) { + bci.x--; + } else { + bci.x++; + } + ok = true; + } + } else { + if (bci.bbox.min()[NR::X] > bci.screen.min()[NR::X]) { + aborted = true; break; + } else { + reached_screen_boundary = true; + } + } + } while (ok); + + if (aborted) { return SCANLINE_CHECK_ABORTED; } + if (reached_screen_boundary) { return SCANLINE_CHECK_BOUNDARY; } + return SCANLINE_CHECK_OK; +} + static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *event) { SPDesktop *desktop = event_context->desktop; SPDocument *document = sp_desktop_document(desktop); @@ -357,9 +508,9 @@ static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *even sp_document_ensure_up_to_date (document); SPItem *document_root = SP_ITEM(SP_DOCUMENT_ROOT(document)); - NR::Rect bbox = document_root->invokeBbox(NR::identity()); + NR::Maybe bbox = document_root->getBounds(NR::identity()); - if (bbox.isEmpty()) { + if (!bbox) { desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Area is not bounded, cannot fill.")); return; } @@ -397,14 +548,32 @@ static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *even nr_arena_item_invoke_update(root, &final_bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE); guchar *px = g_new(guchar, 4 * width * height); - memset(px, 0x00, 4 * width * height); - + //memset(px, 0x00, 4 * width * height); + NRPixBlock B; nr_pixblock_setup_extern( &B, NR_PIXBLOCK_MODE_R8G8B8A8N, final_bbox.x0, final_bbox.y0, final_bbox.x1, final_bbox.y1, px, 4 * width, FALSE, FALSE ); - nr_arena_item_invoke_render( root, &final_bbox, &B, NR_ARENA_ITEM_RENDER_NO_CACHE ); + SPNamedView *nv = sp_desktop_namedview(desktop); + unsigned long bgcolor = nv->pagecolor; + + unsigned char dtc[4]; + dtc[0] = NR_RGBA32_R(bgcolor); + dtc[1] = NR_RGBA32_G(bgcolor); + dtc[2] = NR_RGBA32_B(bgcolor); + dtc[3] = NR_RGBA32_A(bgcolor); + + for (int fy = 0; fy < height; fy++) { + guchar *p = NR_PIXBLOCK_PX(&B) + fy * B.rs; + for (int fx = 0; fx < width; fx++) { + for (int i = 0; i < 4; i++) { + *p++ = dtc[i]; + } + } + } + + nr_arena_item_invoke_render(NULL, root, &final_bbox, &B, NR_ARENA_ITEM_RENDER_NO_CACHE ); nr_pixblock_release(&B); // Hide items @@ -431,7 +600,38 @@ static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *even unsigned char *orig_px = get_pixel(px, (int)pw[NR::X], (int)pw[NR::Y], width); for (int i = 0; i < 4; i++) { orig_color[i] = orig_px[i]; } - int tolerance = (255 * prefs_get_int_attribute_limited("tools.paintbucket", "tolerance", 1, 0, 100)) / 100; + unsigned char merged_orig[4]; + + merge_pixel_with_background(orig_color, dtc, merged_orig); + + PaintBucketChannels method = (PaintBucketChannels)prefs_get_int_attribute("tools.paintbucket", "channels", 0); + int threshold = prefs_get_int_attribute_limited("tools.paintbucket", "threshold", 1, 0, 100); + + switch(method) { + case FLOOD_CHANNELS_ALPHA: + case FLOOD_CHANNELS_RGB: + case FLOOD_CHANNELS_R: + case FLOOD_CHANNELS_G: + case FLOOD_CHANNELS_B: + threshold = (255 * threshold) / 100; + break; + case FLOOD_CHANNELS_H: + case FLOOD_CHANNELS_S: + case FLOOD_CHANNELS_L: + break; + } + + bool reached_screen_boundary = false; + + bitmap_coords_info bci; + + bci.y_limit = y_limit; + bci.width = width; + bci.threshold = threshold; + bci.method = method; + bci.bbox = *bbox; + bci.screen = screen; + bci.dtc = dtc; while (!fill_queue.empty() && !aborted) { NR::Point cp = fill_queue.front(); @@ -439,61 +639,69 @@ static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *even unsigned char *s = get_pixel(px, (int)cp[NR::X], (int)cp[NR::Y], width); // same color at this point - if (compare_pixels(s, orig_color, tolerance)) { - int left = (int)cp[NR::X]; - int right = (int)cp[NR::X] + 1; + if (compare_pixels(s, orig_color, dtc, threshold, method)) { int x = (int)cp[NR::X]; int y = (int)cp[NR::Y]; + bool top_fill = true; + bool bottom_fill = true; + if (y > 0) { - try_add_to_queue(&fill_queue, px, orig_color, x, y - 1, width, tolerance); + top_fill = try_add_to_queue(&fill_queue, px, trace_px, orig_color, dtc, x, y - 1, width, threshold, method, top_fill); } else { - aborted = true; break; + if (bbox->min()[NR::Y] > screen.min()[NR::Y]) { + aborted = true; break; + } else { + reached_screen_boundary = true; + } } if (y < y_limit) { - try_add_to_queue(&fill_queue, px, orig_color, x, y + 1, width, tolerance); + bottom_fill = try_add_to_queue(&fill_queue, px, trace_px, orig_color, dtc, x, y + 1, width, threshold, method, bottom_fill); } else { - aborted = true; break; - } - - unsigned char *t, *trace_t; - bool ok = false; - - do { - ok = false; - // go left - if (left >= 0) { - t = get_pixel(px, left, y, width); - if (compare_pixels(t, orig_color, tolerance)) { - for (int i = 0; i < 4; i++) { t[i] = 255 - t[i]; } - trace_t = get_pixel(trace_px, left, y, width); - trace_t[3] = 255; - if (y > 0) { try_add_to_queue(&fill_queue, px, orig_color, left, y - 1, width, tolerance); } - if (y < y_limit) { try_add_to_queue(&fill_queue, px, orig_color, left, y + 1, width, tolerance); } - left--; ok = true; - } - } else { + if (bbox->max()[NR::Y] < screen.max()[NR::Y]) { aborted = true; break; - } - } while (ok); - - do { - ok = false; - // go right - if (right < width) { - t = get_pixel(px, right, y, width); - if (compare_pixels(t, orig_color, tolerance)) { - for (int i = 0; i < 4; i++) { t[i] = 255 - t[i]; } - trace_t = get_pixel(trace_px, right, y, width); - trace_t[3] = 255; - if (y > 0) { try_add_to_queue(&fill_queue, px, orig_color, right, y - 1, width, tolerance); } - if (y < y_limit) { try_add_to_queue(&fill_queue, px, orig_color, right, y + 1, width, tolerance); } - right++; ok = true; - } } else { - aborted = true; break; + reached_screen_boundary = true; } - } while (ok); + } + + bci.is_left = true; + bci.x = x; + bci.y = y; + bci.top_fill = top_fill; + bci.bottom_fill = bottom_fill; + + ScanlineCheckResult result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci); + + switch (result) { + case SCANLINE_CHECK_ABORTED: + aborted = true; + break; + case SCANLINE_CHECK_BOUNDARY: + reached_screen_boundary = true; + break; + default: + break; + } + + bci.is_left = false; + bci.x = x + 1; + bci.y = y; + bci.top_fill = top_fill; + bci.bottom_fill = bottom_fill; + + result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci); + + switch (result) { + case SCANLINE_CHECK_ABORTED: + aborted = true; + break; + case SCANLINE_CHECK_BOUNDARY: + reached_screen_boundary = true; + break; + default: + break; + } } } @@ -505,6 +713,10 @@ static void sp_flood_do_flood_fill(SPEventContext *event_context, GdkEvent *even return; } + if (reached_screen_boundary) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Only the visible part of the bounded area was filled. If you want to fill all of the area, undo, zoom out, and fill again.")); + } + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(trace_px, GDK_COLORSPACE_RGB, TRUE, @@ -525,10 +737,22 @@ static gint sp_flood_context_item_handler(SPEventContext *event_context, SPItem { gint ret = FALSE; + SPDesktop *desktop = event_context->desktop; + switch (event->type) { case GDK_BUTTON_PRESS: + if (event->button.state & GDK_CONTROL_MASK) { + NR::Point const button_w(event->button.x, + event->button.y); + + SPItem *item = sp_event_context_find_item (desktop, button_w, TRUE, TRUE); + + Inkscape::XML::Node *pathRepr = SP_OBJECT_REPR(item); + /* Set style */ + sp_desktop_apply_style_tool (desktop, pathRepr, "tools.paintbucket", false); + ret = TRUE; + } break; - // motion and release are always on root (why?) default: break; } @@ -542,15 +766,25 @@ static gint sp_flood_context_item_handler(SPEventContext *event_context, SPItem static gint sp_flood_context_root_handler(SPEventContext *event_context, GdkEvent *event) { + gint ret = FALSE; SPDesktop *desktop = event_context->desktop; - gint ret = FALSE; switch (event->type) { case GDK_BUTTON_PRESS: if ( event->button.button == 1 ) { - sp_flood_do_flood_fill(event_context, event); - - ret = TRUE; + if (!(event->button.state & GDK_CONTROL_MASK)) { + // set "busy" cursor + GdkCursor *waiting = gdk_cursor_new(GDK_WATCH); + gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, waiting); + + sp_flood_do_flood_fill(event_context, event); + + // restore cursor when done + gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, event_context->cursor); + gdk_cursor_unref(waiting); + + ret = TRUE; + } } break; case GDK_KEY_PRESS: @@ -563,8 +797,6 @@ static gint sp_flood_context_root_handler(SPEventContext *event_context, GdkEven if (!MOD__CTRL_ONLY) ret = TRUE; break; - case GDK_Escape: - sp_desktop_selection(desktop)->clear(); default: break; } @@ -604,6 +836,11 @@ static void sp_flood_finish(SPFloodContext *rc) } } +void flood_channels_changed(GtkComboBox *cbox, GtkWidget *tbl) +{ + prefs_set_int_attribute("tools.paintbucket", "channels", (gint)gtk_combo_box_get_active(cbox)); +} + /* Local Variables: mode:c++