27#include <gdk/gdkkeysyms.h>
28#include <glibmm/i18n.h>
89 NC_(
"Flood autogap",
"None"),
90 NC_(
"Flood autogap",
"Small"),
91 NC_(
"Flood autogap",
"Medium"),
92 NC_(
"Flood autogap",
"Large")
112 if (prefs->getBool(
"/tools/paintbucket/selcue")) {
142 uint32_t ap = 0, rp = 0, gp = 0, bp = 0;
143 uint32_t rb = 0, gb = 0, bb = 0;
144 ExtractARGB32(px, ap, rp, gp, bp);
145 ExtractRGB32(bg, rb, gb, bb);
147 uint32_t ro = (255 - ap) * rb + 255 * rp; ro = (ro + 127) / 255;
148 uint32_t go = (255 - ap) * gb + 255 * gp; go = (go + 127) / 255;
149 uint32_t bo = (255 - ap) * bb + 255 * bp; bo = (bo + 127) / 255;
151 return AssembleARGB32(255, ro, go, bo);
163 return *
reinterpret_cast<uint32_t*
>(px + y *
stride + x * 4);
168 return trace_px + (x + y *
width);
182 return std::abs(
static_cast<int64_t
>(a) -
static_cast<int64_t
>(b)) <=
static_cast<int64_t
>(d);
196 uint32_t ac = 0,
rc = 0, gc = 0, bc = 0;
197 ExtractARGB32(check, ac,
rc, gc, bc);
199 uint32_t ao = 0, ro = 0, go = 0, bo = 0;
200 ExtractARGB32(
orig, ao, ro, go, bo);
202 uint32_t ad = 0,
rd = 0, gd = 0, bd = 0;
203 ExtractARGB32(dtc, ad,
rd, gd, bd);
205 uint32_t amop = 0, rmop = 0, gmop = 0, bmop = 0;
206 ExtractARGB32(merged_orig_pixel, amop, rmop, gmop, bmop);
208 auto hsl_orig =
Color(Space::Type::HSL, {0, 0, 0});
209 auto hsl_check = hsl_orig;
216 hsl_orig.
set(
Color(Space::Type::RGB, {ro / dao, go / dao, bo / dao}),
true);
217 hsl_check.
set(
Color(Space::Type::RGB, {
rc / dac, gc / dac, bc / dac}),
true);
237 uint32_t amc, rmc, bmc, gmc;
241 rmc = (255-ac)*
rd + 255*
rc; rmc = (rmc + 127) / 255;
242 gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255;
243 bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255;
249 return ((diff / 3) <= ((threshold * 3) / 4));
252 return ((
int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold);
254 return ((
int)(fabs(hsl_check[1] - hsl_orig[1]) * 100.0) <= threshold);
256 return ((
int)(fabs(hsl_check[2] - hsl_orig[2]) * 100.0) <= threshold);
284struct BitmapCoordsInfo
293 unsigned int threshold;
297 uint32_t merged_orig_pixel;
300 unsigned int max_queue_size;
301 unsigned int current_step;
313inline static bool check_if_pixel_is_paintable(
unsigned char *px,
unsigned char *trace_t,
int x,
int y, uint32_t orig_color, BitmapCoordsInfo
const &bci)
318 uint32_t pixel =
get_pixel(px, x, y, bci.stride);
319 if (
compare_pixels(pixel, orig_color, bci.merged_orig_pixel, bci.dtc, bci.threshold, bci.method)) {
336static void do_trace(BitmapCoordsInfo
const &bci,
unsigned char *trace_px,
SPDesktop *
desktop,
Geom::Affine const &transform,
unsigned min_x,
unsigned max_x,
unsigned min_y,
unsigned max_y,
bool union_with_selection)
340 unsigned char *trace_t;
342 auto gray_map =
Trace::GrayMap(max_x - min_x + 1, max_y - min_y + 1);
343 unsigned gray_map_y = 0;
344 for (
unsigned y = min_y; y <= max_y; y++) {
345 auto gray_map_t = gray_map.row(gray_map_y);
348 for (
unsigned x = min_x; x <= max_x; x++) {
366 for (
auto result : results) {
378 path.
Fill(&path_shape, 0);
380 Shape expanded_path_shape;
407 cast<SPItem>(reprobj)->doWriteTransform(transform);
412 gchar
const *t_str = pathRepr->
attribute(
"transform");
425 if (union_with_selection) {
427 ngettext(
"Area filled, path with <b>%d</b> node created and unioned with selection.",
"Area filled, path with <b>%d</b> nodes created and unioned with selection.",
428 cast<SPPath>(reprobj)->nodesInPath()), cast<SPPath>(reprobj)->nodesInPath() );
429 selection->
add(reprobj);
433 ngettext(
"Area filled, path with <b>%d</b> node created.",
"Area filled, path with <b>%d</b> nodes created.",
434 cast<SPPath>(reprobj)->nodesInPath()), cast<SPPath>(reprobj)->nodesInPath() );
435 selection->
set(reprobj);
461inline static bool coords_in_range(
unsigned x,
unsigned y, BitmapCoordsInfo
const &bci)
463 return x < bci.width && y < bci.height;
480inline static unsigned paint_pixel(
unsigned char *px,
unsigned char *trace_px, uint32_t orig_color, BitmapCoordsInfo
const &bci,
unsigned char *original_point_trace_t)
482 if (bci.radius == 0) {
486 unsigned char *trace_t;
488 bool can_paint_up =
true;
489 bool can_paint_down =
true;
490 bool can_paint_left =
true;
491 bool can_paint_right =
true;
493 for (
unsigned int ty = bci.y - bci.radius; ty <= bci.y + bci.radius; ty++) {
494 for (
unsigned int tx = bci.x - bci.radius; tx <= bci.x + bci.radius; tx++) {
501 if (tx < bci.x) { can_paint_left =
false; }
502 if (tx > bci.x) { can_paint_right =
false; }
503 if (ty < bci.y) { can_paint_up =
false; }
504 if (ty > bci.y) { can_paint_down =
false; }
511 unsigned int paint_directions = 0;
517 return paint_directions;
529static void push_point_onto_queue(std::deque<Geom::Point> &fill_queue,
unsigned max_queue_size,
unsigned char *trace_t,
unsigned x,
unsigned y)
532 fill_queue.emplace_back(x, y);
545static void shift_point_onto_queue(std::deque<Geom::Point> &fill_queue,
unsigned max_queue_size,
unsigned char *trace_t,
unsigned x,
unsigned y)
548 fill_queue.emplace_front(x, y);
563 bool aborted =
false;
564 bool reached_screen_boundary =
false;
568 bool initial_paint =
true;
570 unsigned char *current_trace_t =
get_trace_pixel(trace_px, bci.x, bci.y, bci.width);
571 unsigned int paint_directions;
573 bool currently_painting_top =
false;
574 bool currently_painting_bottom =
false;
576 unsigned int top_ty = (bci.y > 0) ? bci.y - 1 : 0;
577 unsigned int bottom_ty = bci.y + 1;
579 bool can_paint_top = (top_ty > 0);
580 bool can_paint_bottom = (bottom_ty < bci.height);
587 keep_tracing = bci.x != 0;
589 keep_tracing = bci.x < bci.width;
592 *min_x = std::min(*min_x, bci.x);
593 *max_x = std::max(*max_x, bci.x);
597 paint_directions =
paint_pixel(px, trace_px, orig_color, bci, current_trace_t);
598 if (bci.radius == 0) {
600 if (!fill_queue.empty() && front_of_queue ==
Geom::IntPoint(bci.x, bci.y)) {
601 fill_queue.pop_front();
602 front_of_queue = fill_queue.empty() ?
Geom::Point() : fill_queue.front();
608 unsigned char *trace_t = current_trace_t - bci.width;
612 if (initial_paint) { currently_painting_top = !ok_to_paint; }
614 if (ok_to_paint && (!currently_painting_top)) {
615 currently_painting_top =
true;
618 if ((!ok_to_paint) && currently_painting_top) {
619 currently_painting_top =
false;
625 if (can_paint_bottom) {
627 unsigned char *trace_t = current_trace_t + bci.width;
631 if (initial_paint) { currently_painting_bottom = !ok_to_paint; }
633 if (ok_to_paint && (!currently_painting_bottom)) {
634 currently_painting_bottom =
true;
637 if ((!ok_to_paint) && currently_painting_bottom) {
638 currently_painting_bottom =
false;
646 bci.x--; current_trace_t--;
651 bci.x++; current_trace_t++;
656 initial_paint =
false;
660 aborted =
true;
break;
662 reached_screen_boundary =
true;
677 return a.
y() > b.
y();
685 return a.
x() > b.
x();
697 bool union_with_selection,
bool is_point_fill,
bool is_touch_fill)
702 auto const bbox = document->getRoot()->visualBounds();
710 constexpr double padding = 1.6;
719 auto const width = img_dims.
x();
720 auto const height = img_dims.
y();
722 auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::ARGB32,
width);
724 auto const px = std::make_unique<unsigned char[]>(
stride *
height);
732 auto root = document->getRoot()->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
733 root->setTransform(doc2img);
737 drawing.
update(final_bbox);
739 auto surf = Cairo::ImageSurface::create(px.get(), Cairo::Surface::Format::ARGB32,
width,
height,
stride);
744 auto bgcolor = document->getPageManager().getBackgroundColor();
745 bgcolor.setOpacity(0.0);
747 dc.setSource(bgcolor.toARGB());
748 dc.setOperator(CAIRO_OPERATOR_SOURCE);
750 dc.setOperator(CAIRO_OPERATOR_OVER);
752 drawing.
render(dc, final_bbox);
754 if constexpr (
false) surf->write_to_png(
"cairo.png");
759 document->getRoot()->invoke_hide(dkey);
762 if constexpr (
false) {
764 auto surf = Cairo::ImageSurface::create(px.get(), Cairo::Surface::Format::ARGB32,
width,
height,
stride);
765 surf->write_to_png(
"cairo2.png");
766 std::cout <<
" Wrote cairo2.png" << std::endl;
769 auto const trace_px = std::make_unique<unsigned char[]>(
width *
height);
771 std::deque<Geom::Point> fill_queue;
772 std::queue<Geom::Point> color_queue;
774 bool aborted =
false;
778 auto const method =
static_cast<PaintBucketChannels>(prefs->getInt(
"/tools/paintbucket/channels", 0));
779 int threshold = prefs->getIntLimited(
"/tools/paintbucket/threshold", 1, 0, 100);
787 threshold = (255 * threshold) / 100;
793 BitmapCoordsInfo bci;
795 bci.y_limit = y_limit;
799 bci.threshold = threshold;
804 bci.radius = prefs->getIntLimited(
"/tools/paintbucket/autogap", 0, 0, 3);
806 bci.current_step = 0;
808 auto const fill_points = [&] () -> std::vector<Geom::Point> {
810 return { cursor_pos };
818 for (
unsigned i = 0; i < fill_points.size(); i++) {
819 auto const pw = img_max_indices.clamp(fill_points[i] * world2img);
823 color_queue.emplace(pw);
829 color_queue.emplace(pw);
833 bool reached_screen_boundary =
false;
835 bool first_run =
true;
837 size_t sort_size_threshold = 5;
839 unsigned int min_y =
height;
840 unsigned int max_y = 0;
841 unsigned int min_x =
width;
842 unsigned int max_x = 0;
844 while (!color_queue.empty() && !aborted) {
848 int cx = (int)color_point[
Geom::X];
849 int cy = (int)color_point[
Geom::Y];
860 for (
unsigned int y = 0; y <
height; y++) {
862 for (
unsigned int x = 0; x <
width; x++) {
872 unsigned long old_fill_queue_size = fill_queue.size();
874 while (!fill_queue.empty() && !aborted) {
877 if (bci.radius == 0) {
878 auto const new_fill_queue_size = fill_queue.size();
888 if (new_fill_queue_size > sort_size_threshold) {
889 if (new_fill_queue_size > old_fill_queue_size) {
892 auto start_sort = fill_queue.begin();
893 auto end_sort = fill_queue.begin();
894 unsigned int sort_y = (
unsigned int)cp[
Geom::Y];
895 unsigned int current_y;
897 for (
auto i = fill_queue.begin(); i != fill_queue.end(); ++i) {
900 if (current_y != sort_y) {
901 if (start_sort != end_sort) {
909 if (start_sort != end_sort) {
913 cp = fill_queue.front();
917 old_fill_queue_size = new_fill_queue_size;
920 fill_queue.pop_front();
925 min_y =
MIN((
unsigned int)y, min_y);
926 max_y =
MAX((
unsigned int)y, max_y);
934 aborted =
true;
break;
936 reached_screen_boundary =
true;
942 aborted =
true;
break;
944 reached_screen_boundary =
true;
959 reached_screen_boundary =
true;
979 reached_screen_boundary =
true;
990 if (bci.current_step > bci.max_queue_size) {
1001 if (reached_screen_boundary) {
1005 unsigned int trace_padding = bci.radius + 1;
1006 if (min_y > trace_padding) { min_y -= trace_padding; }
1007 if (max_y < (y_limit - trace_padding)) { max_y += trace_padding; }
1008 if (min_x > trace_padding) { min_x -= trace_padding; }
1009 if (max_x < (
width - 1 - trace_padding)) { max_x += trace_padding; }
1013 do_trace(bci, trace_px.get(),
desktop, inverted_affine, min_x, max_x, min_y, max_y, union_with_selection);
1024 if (event.num_press == 1 && event.button == 1 && event.
modifiers & GDK_CONTROL_MASK) {
1025 auto const button_w = event.pos;
1027 auto item = sp_event_context_find_item(_desktop, button_w, true, true);
1030 _desktop->applyCurrentOrToolStyle(item,
"/tools/paintbucket", false);
1032 DocumentUndo::done(_desktop->getDocument(), _(
"Set style on object"), INKSCAPE_ICON(
"color-fill"));
1048 if (event.num_press == 1 && event.button == 1 && !(event.
modifiers & GDK_CONTROL_MASK)) {
1049 if (have_viable_layer(_desktop, defaultMessageContext())) {
1051 saveDragOrigin(event.pos);
1055 auto const p = _desktop->w2d(event.pos);
1056 auto rubberband = Rubberband::get(_desktop);
1057 rubberband->setMode(Rubberband::Mode::TOUCHPATH);
1058 rubberband->setHandle(RUBBERBAND_TOUCHPATH_FLOOD);
1059 rubberband->start(_desktop, p);
1065 if (dragging && event.modifiers & GDK_BUTTON1_MASK) {
1066 if (!checkDragMoved(event.pos)) {
1070 auto const p = _desktop->w2d(event.pos);
1074 defaultMessageContext()->set(
NORMAL_MESSAGE, _(
"<b>Draw over</b> areas to add to fill, hold <b>Alt</b> for touch fill"));
1081 if (event.button == 1) {
1084 if (r->isStarted()) {
1086 bool is_point_fill = within_tolerance;
1087 bool is_touch_fill =
event.modifiers & GDK_ALT_MASK;
1095 event.modifiers & GDK_SHIFT_MASK,
1096 is_point_fill, is_touch_fill);
1102 ToolBase *current_context = current_desktop->
getTool();
1104 if (current_context == (ToolBase*)
this) {
1105 this->defaultMessageContext()->clear();
1117 case GDK_KEY_KP_Down:
1134void FloodTool::finishItem()
1136 message_context->clear();
1141 _desktop->getSelection()->set(
item);
1142 DocumentUndo::done(_desktop->getDocument(), _(
"Fill bounded area"), INKSCAPE_ICON(
"color-fill"));
1148void FloodTool::set_channels(
int channels)
1151 prefs->setInt(
"/tools/paintbucket/channels", channels);
TODO: insert short description here.
TODO: insert short description here.
Cairo integration helpers.
G_GNUC_CONST guint32 unpremul_alpha(const guint32 color, const guint32 alpha)
3x3 matrix representing an affine transformation.
bool isIdentity(Coord eps=EPSILON) const
Check whether this matrix is an identity matrix.
Affine inverse() const
Compute the inverse matrix.
static CRect from_xywh(C x, C y, C w, C h)
Create rectangle from origin and dimensions.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Two-dimensional point with integer coordinates.
constexpr IntCoord x() const noexcept
constexpr IntCoord y() const noexcept
Two-dimensional point that doubles as a vector.
constexpr Coord y() const noexcept
constexpr Coord x() const noexcept
Axis aligned, non-empty rectangle.
A dummy Progress object that never reports cancellation.
bool set(unsigned int index, double value)
Set a specific channel in the color.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
Minimal wrapper over Cairo.
void setRoot(DrawingItem *root)
void update(Geom::IntRect const &area=Geom::IntRect::infinite(), Geom::Affine const &affine=Geom::identity(), unsigned flags=DrawingItem::STATE_ALL, unsigned reset=0)
void render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags=0) const
SPGroup * currentLayer() const
Returns current top layer.
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
MessageId flashF(MessageType type, char const *format,...) G_GNUC_PRINTF(3
temporarily pushes a message onto the stack using printf-like formatting
void pathUnion(bool skip_undo=false, bool silent=false)
SPItem * singleItem()
Returns a single selected item.
Preference storage class.
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
static Preferences * get()
Access the singleton Preferences object.
std::vector< Geom::Point > getPoints() const
static Rubberband * get(SPDesktop *desktop)
void move(Geom::Point const &p)
The set of selected SPObjects for a given document and layer model.
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
TraceResult traceGrayMap(GrayMap const &grayMap, Async::Progress< double > &progress)
This allows routines that already generate GrayMaps to skip image filtering, increasing performance.
void set_item(SPItem *item)
void unset_item(bool keep_knotholder=false)
Interface for refcounted XML nodes.
virtual void setPosition(int pos)=0
Set the position of this node in parent's child order.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Path and its polyline approximation.
void ConvertWithBackData(double threshhold, bool relative=false)
Creates a polyline approximation of the path.
void Fill(Shape *dest, int pathID=-1, bool justAdd=false, bool closeIfNeeded=true, bool invert=false)
Fills the shape with the polyline approximation stored in this object.
std::string svg_dump_path() const
void ConvertEvenLines(double treshhold)
Creates a polyline approximation of the path.
void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation)
Load a lib2geom Geom::PathVector in this path object.
void Simplify(double treshhold)
Simplify the path.
To do: update description of desktop.
Inkscape::UI::Widget::Canvas * getCanvas() const
double current_zoom() const
SPDocument * getDocument() const
Inkscape::MessageStack * messageStack() const
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Inkscape::Selection * getSelection() const
Inkscape::UI::Tools::ToolBase * getTool() const
void clearWaitingCursor()
Inkscape::LayerManager & layerManager()
Geom::Affine const & doc2dt() const
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Typed SVG document implementation.
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
static unsigned int display_key_new(unsigned numkeys)
Allocates unique integer keys.
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void addChild(Inkscape::XML::Node *child, Inkscape::XML::Node *prev=nullptr)
A class to store/manipulate directed graphs.
void ConvertToForme(Path *dest)
Extract contours from a directed graph.
int MakeOffset(Shape *of, double dec, JoinType join, double miter, bool do_profile=false, double cx=0, double cy=0, double radius=0, Geom::Affine *i2doc=nullptr)
int ConvertToShape(Shape *a, FillRule directed=fill_nonZero, bool invert=false)
Using a given fill rule, find all intersections in the shape given, create a new intersection free sh...
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
Editable view implementation.
static char const *const current
TODO: insert short description here.
Cairo drawing context with Inkscape extensions.
Macro for icon names used in Inkscape.
TODO: insert short description here.
Interface for locally managing a current status message.
Raw stack of active status messages.
Affine identity()
Create an identity matrix.
A set of useful color modifying functions which do not fit as generic methods on the color class itse...
void ExtractARGB32(guint32 px, guint32 &a, guint32 &r, guint32 &g, guint &b)
void ExtractRGB32(guint32 px, guint32 &r, guint32 &g, guint &b)
guint AssembleARGB32(guint32 a, guint32 r, guint32 g, guint32 b)
static R & release(R &r)
Decrements the reference count of a anchored object.
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
bool mod_ctrl_only(unsigned modifiers)
PathVector - a sequence of subpaths.
Inkscape::ShapeEditor This is a container class which contains a knotholder for shapes.
SPRoot: SVG <svg> implementation.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
Movement of the mouse pointer.
static unsigned long constexpr WHITE
static unsigned long constexpr BLACK
Interface for XML documents.
virtual Node * createElement(char const *name)=0
bool sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
std::string sp_svg_transform_write(Geom::Affine const &transform)