diff options
Diffstat (limited to '')
-rw-r--r-- | src/live_effects/lpe-tiling.cpp | 1639 |
1 files changed, 1639 insertions, 0 deletions
diff --git a/src/live_effects/lpe-tiling.cpp b/src/live_effects/lpe-tiling.cpp new file mode 100644 index 0000000..c094732 --- /dev/null +++ b/src/live_effects/lpe-tiling.cpp @@ -0,0 +1,1639 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * LPE <tiling> implementation + */ +/* + * Authors: + * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es> + * Adam Belis <> + * Copyright (C) Authors 2022-2022 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "live_effects/lpe-tiling.h" + +#include <2geom/intersection-graph.h> +#include <2geom/path-intersection.h> +#include <2geom/sbasis-to-bezier.h> +#include <gdk/gdk.h> +#include <gtkmm.h> + +#include "display/curve.h" +#include "helper/geom.h" +#include "live_effects/lpeobject.h" +#include "live_effects/parameter/satellite-reference.h" +#include "object/sp-object.h" +#include "object/sp-path.h" +#include "object/sp-shape.h" +#include "object/sp-text.h" +#include "path-chemistry.h" +#include "path/path-boolop.h" +#include "style.h" +#include "svg/path-string.h" +#include "svg/svg.h" +#include "xml/sp-css-attr.h" +#include "ui/knot/knot-holder.h" +#include "ui/knot/knot-holder-entity.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +// TODO due to internal breakage in glibmm headers, this must be last: +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace LivePathEffect { + +namespace CoS { + class KnotHolderEntityCopyGapX : public LPEKnotHolderEntity { + public: + KnotHolderEntityCopyGapX(LPETiling * effect) : LPEKnotHolderEntity(effect) {}; + ~KnotHolderEntityCopyGapX() override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override; + void knot_click(guint state) override; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override; + Geom::Point knot_get() const override; + double startpos = dynamic_cast<LPETiling const*> (_effect)->gapx_unit; + }; + class KnotHolderEntityCopyGapY : public LPEKnotHolderEntity { + public: + KnotHolderEntityCopyGapY(LPETiling * effect) : LPEKnotHolderEntity(effect) {}; + ~KnotHolderEntityCopyGapY() override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override; + void knot_click(guint state) override; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override; + Geom::Point knot_get() const override; + double startpos = dynamic_cast<LPETiling const*> (_effect)->gapy_unit; + }; +} // CoS + +LPETiling::LPETiling(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + // do not change name of this parameter is used in oncommit + unit(_("Unit:"), _("Unit"), "unit", &wr, this, "px"), + lpesatellites(_("lpesatellites"), _("Items satellites"), "lpesatellites", &wr, this, false), + num_cols(_("Columns"), _("Number of columns"), "num_cols", &wr, this, 3), + num_rows(_("Rows"), _("Number of rows"), "num_rows", &wr, this, 3), + gapx(_("Gap X"), _("Horizontal gap between tiles (uses selected unit)"), "gapx", &wr, this, 0.0), + gapy(_("Gap Y"), _("Vertical gap between tiles (uses selected unit)"), "gapy", &wr, this, 0.0), + scale(_("Scale %"), _("Scale tiles by this percentage"), "scale", &wr, this, 0.0), + rotate(_("Rotate °"), _("Rotate tiles by this amount of degrees"), "rotate", &wr, this, 0.0), + offset(_("Offset %"), _("Offset tiles by this percentage of width/height"), "offset", &wr, this, 0.0), + offset_type(_("Offset type"), _("Choose whether to offset rows or columns"), "offset_type", &wr, this, false), + interpolate_scalex(_("Interpolate scale X"), _("Interpolate tile size in each row"), "interpolate_scalex", &wr, this, false), + interpolate_scaley(_("Interpolate scale Y"), _("Interpolate tile size in each column"), "interpolate_scaley", &wr, this, true), + shrink_interp(_("Minimize gaps"), _("Minimize gaps between scaled objects (does not work with rotation/diagonal mode)"), "shrink_interp", &wr, this, false), + interpolate_rotatex(_("Interpolate rotation X"), _("Interpolate tile rotation in row"), "interpolate_rotatex", &wr, this, false), + interpolate_rotatey(_("Interpolate rotation Y"), _("Interpolate tile rotation in column"), "interpolate_rotatey", &wr, this, true), + split_items(_("Split elements"), _("Split elements, so they can be selected, styled, and moved (if grouped) independently"), "split_items", &wr, this, false), + mirrorrowsx(_("Mirror rows in X"), _("Mirror rows horizontally"), "mirrorrowsx", &wr, this, false), + mirrorrowsy(_("Mirror rows in Y"), _("Mirror rows vertically"), "mirrorrowsy", &wr, this, false), + mirrorcolsx(_("Mirror cols in X"), _("Mirror columns horizontally"), "mirrorcolsx", &wr, this, false), + mirrorcolsy(_("Mirror cols in Y"), _("Mirror columns vertically"), "mirrorcolsy", &wr, this, false), + mirrortrans(_("Mirror transforms"), _("Mirror transformations"), "mirrortrans", &wr, this, false), + link_styles(_("Link styles"), _("Link styles in split mode, can also be used to reset style of copies"), "link_styles", &wr, this, false), + random_gap_x(_("Random gaps X"), _("Randomize horizontal gaps"), "random_gap_x", &wr, this, false), + random_gap_y(_("Random gaps Y"), _("Randomize vertical gaps"), "random_gap_y", &wr, this, false), + random_rotate(_("Random rotation"), _("Randomize tile rotation"), "random_rotate", &wr, this, false), + random_scale(_("Random scale"), _("Randomize scale"), "random_scale", &wr, this, false), + seed(_("Seed"), _("Randomization seed"), "seed", &wr, this, 1.), + transformorigin("transformorigin:", "transformorigin","transformorigin", &wr, this, "", true) +{ + show_orig_path = true; + _provides_knotholder_entities = true; + // register all your parameters here, so Inkscape knows which parameters this effect has: + // please intense work on this widget and is important reorder parameters very carefully + registerParameter(&unit); + registerParameter(&seed); + registerParameter(&lpesatellites); + registerParameter(&num_rows); + registerParameter(&num_cols); + registerParameter(&gapx); + registerParameter(&gapy); + registerParameter(&offset); + registerParameter(&offset_type); + registerParameter(&scale); + registerParameter(&rotate); + registerParameter(&mirrorrowsx); + registerParameter(&mirrorrowsy); + registerParameter(&mirrorcolsx); + registerParameter(&mirrorcolsy); + registerParameter(&mirrortrans); + registerParameter(&shrink_interp); + registerParameter(&split_items); + registerParameter(&link_styles); + registerParameter(&interpolate_scalex); + registerParameter(&interpolate_scaley); + registerParameter(&interpolate_rotatex); + registerParameter(&interpolate_rotatey); + registerParameter(&random_scale); + registerParameter(&random_rotate); + registerParameter(&random_gap_y); + registerParameter(&random_gap_x); + registerParameter(&transformorigin); + + num_cols.param_set_range(1, 9999);// we need the input a bit tiny so this seems enough + num_cols.param_make_integer(); + num_cols.param_set_increments(1, 10); + num_rows.param_set_range(1, 9999); + num_rows.param_make_integer(); + num_rows.param_set_increments(1, 10); + scale.param_set_range(-9999.99,9999.99); + scale.param_set_increments(1, 10); + gapx.param_set_range(-99999,99999); + gapx.param_set_increments(1.0, 10.0); + gapy.param_set_range(-99999,99999); + gapy.param_set_increments(1.0, 10.0); + rotate.param_set_increments(1.0, 10.0); + rotate.param_set_range(-900, 900); + offset.param_set_range(-300, 300); + offset.param_set_increments(1.0, 10.0); + seed.param_set_range(1.0, 1.0); + seed.param_set_randomsign(true); + apply_to_clippath_and_mask = true; + _provides_knotholder_entities = true; + prev_num_cols = num_cols; + prev_num_rows = num_rows; + _knotholder = nullptr; + reset = link_styles; + prev_unit = unit.get_abbreviation(); +} + +LPETiling::~LPETiling() +{ + keep_paths = false; + doOnRemove(nullptr); +}; + +bool LPETiling::doOnOpen(SPLPEItem const *lpeitem) +{ + bool fixed = false; + if (!is_load || is_applied) { + return fixed; + } + if (!split_items) { + return fixed; + } + lpesatellites.update_satellites(); + container = lpeitem->parent; + return fixed; +} + +void +LPETiling::doAfterEffect (SPLPEItem const* lpeitem, SPCurve *curve) +{ + if (split_items) { + SPDocument *document = getSPDoc(); + if (!document) { + return; + } + bool write = false; + bool active = !lpesatellites.data().size(); + for (auto lpereference : lpesatellites.data()) { + if (lpereference && lpereference->isAttached() && lpereference.get()->getObject() != nullptr) { + active = true; + } + } + if (!active && !is_load && prev_split) { + lpesatellites.clear(); + prev_num_cols = 0; + prev_num_rows = 0; + } + prev_split = split_items; + + container = sp_lpe_item->parent; + if (prev_num_cols * prev_num_rows != num_cols * num_rows) { + write = true; + size_t pos = 0; + for (auto lpereference : lpesatellites.data()) { + if (lpereference && lpereference->isAttached()) { + SPItem *copies = dynamic_cast<SPItem *>(lpereference->getObject()); + if (copies) { + if (pos > num_cols * num_rows - 2) { + copies->setHidden(true); + } else if (copies->isHidden()) { + copies->setHidden(false); + } + } + } + pos++; + } + prev_num_cols = num_cols; + prev_num_rows = num_rows; + } + if (!gap_bbox) { + return; + } + Geom::Point center = (*gap_bbox).midpoint() * transformoriginal.inverse(); + bool forcewrite = false; + Geom::Affine origin = Geom::Translate(center).inverse(); + if (!interpolate_rotatex && !interpolate_rotatey && !random_rotate) { + origin *= Geom::Rotate::from_degrees(rotate); + } + if (!interpolate_scalex && !interpolate_scaley && !random_scale) { + origin *= Geom::Scale(scaleok, scaleok); + } + origin *= Geom::Translate(center); + origin = origin.inverse(); + size_t counter = 0; + double gapscalex = 0; + double maxheight = 0; + double maxwidth = 0; + double minheight = std::numeric_limits<double>::max(); + double y[(int)num_cols]; + double ygap[(int)num_cols]; + double yset = 0; + Geom::OptRect prev_bbox; + Geom::OptRect bbox = sp_lpe_item->geometricBounds(); + + Geom::Affine base_transform = sp_item_transform_repr(sp_lpe_item); + Geom::Affine gapp = base_transform.inverse() * transformoriginal; + Geom::Point spcenter_base = (*sp_lpe_item->geometricBounds(transformoriginal)).midpoint(); + Geom::Point spcenter = (*sp_lpe_item->geometricBounds(base_transform)).midpoint(); + Geom::Affine gap = gapp.withoutTranslation(); + if (!bbox) { + return; + } + (*bbox) *= transformoriginal; + for (int i = 0; i < num_rows; ++i) { + double fracy = 1; + if (num_rows != 1) { + fracy = i/(double)(num_rows - 1); + } + for (int j = 0; j < num_cols; ++j) { + double x = 0; + double fracx = 1; + if (num_cols != 1) { + fracx = j/(double)(num_cols - 1); + } + Geom::Affine r = Geom::identity(); + Geom::Scale mirror = Geom::Scale(1,1); + if(mirrorrowsx || mirrorrowsy || mirrorcolsx || mirrorcolsy) { + gint mx = 1; + gint my = 1; + if (mirrorrowsx && mirrorcolsx) { + mx = (j+i)%2 != 0 ? -1 : 1; + } else { + if (mirrorrowsx) { + mx = i%2 != 0 ? -1 : 1; + } else if (mirrorcolsx) { + mx = j%2 != 0 ? -1 : 1; + } + } + if (mirrorrowsy && mirrorcolsy) { + my = (j+i)%2 != 0 ? -1 : 1; + } else { + if (mirrorrowsy) { + my = i%2 != 0 ? -1 : 1; + } else if (mirrorcolsy) { + my = j%2 != 0 ? -1 : 1; + } + } + mirror = Geom::Scale(mx, my); + } + if (mirrortrans && interpolate_scalex && i%2 != 0) { + fracx = 1-fracx; + } + double fracyin = fracy; + if (mirrortrans && interpolate_scaley && j%2 != 0) { + fracyin = 1-fracyin; + } + double rotatein = rotate; + if (interpolate_rotatex && interpolate_rotatey) { + rotatein = rotatein * (i + j); + } else if (interpolate_rotatex) { + rotatein = rotatein * j; + } else if (interpolate_rotatey) { + rotatein = rotatein * i; + } + if (mirrortrans && + ((interpolate_rotatex && i%2 != 0) || + (interpolate_rotatey && j%2 != 0) || + (interpolate_rotatex && interpolate_rotatey))) + { + rotatein *=-1; + } + double scalein = 1; + double scalegap = scaleok - scalein; + if (interpolate_scalex && interpolate_scaley) { + scalein = (scalegap * (i + j)) + 1; + } else if (interpolate_scalex) { + scalein = (scalegap * j) + 1; + } else if (interpolate_scaley) { + scalein = (scalegap * i) + 1; + } else { + scalein = scaleok; + } + if (!interpolate_rotatex && !interpolate_rotatey && !random_rotate) { + r *= Geom::Rotate::from_degrees(rotatein).inverse(); + } + if (random_scale && scaleok != 1.0) { + if (random_s.size() == counter) { + double max = std::max(1.0,scaleok); + double min = std::min(1.0,scaleok); + random_s.emplace_back(seed.param_get_random_number() * (max - min) + min); + } + scalein = random_s[counter]; + } + if (random_rotate && rotate) { + if (random_r.size() == counter) { + random_r.emplace_back((seed.param_get_random_number() - seed.param_get_random_number()) * rotate); + } + rotatein = random_r[counter]; + } + if (random_x.size() == counter) { + if (random_gap_x && gapx_unit) { + random_x.emplace_back((seed.param_get_random_number() * gapx_unit)); // avoid overlapping + } else { + random_x.emplace_back(0); + } + } + if (random_y.size() == counter) { + if (random_gap_y && gapy_unit) { + random_y.emplace_back((seed.param_get_random_number() * gapy_unit)); // avoid overlapping + } else { + random_y.emplace_back(0); + } + } + r *= Geom::Rotate::from_degrees(rotatein); + r *= Geom::Scale(scalein, scalein); + double scale_fix = end_scale(scaleok, true); + double heightrows = original_height * scale_fix; + double widthcols = original_width * scale_fix; + double fixed_heightrows = heightrows; + double fixed_widthcols = widthcols; + bool shrink_interpove = shrink_interp; + if (rotatein) { + shrink_interpove = false; + } + if (scaleok != 1.0 && (interpolate_scalex || interpolate_scaley)) { + maxheight = std::max(maxheight,(*bbox).height() * scalein); + maxwidth = std::max(maxwidth,(*bbox).width() * scalein); + minheight = std::min(minheight,(*bbox).height() * scalein); + widthcols = std::max(original_width * end_scale(scaleok, false), original_width); + heightrows = std::max(original_height * end_scale(scaleok, false), original_height); + fixed_widthcols = widthcols; + fixed_heightrows = heightrows; + double cx = (*bbox).width() * scalein; + double cy = (*bbox).height() * scalein; + cx += gapx_unit; + cy += gapy_unit; + if (shrink_interpove && (!interpolate_scalex || !interpolate_scaley)) { + double px = 0; + double py = 0; + if (prev_bbox) { + px = (*prev_bbox).width(); + py = (*prev_bbox).height(); + px += gapx_unit; + py += gapy_unit; + } + if (interpolate_scalex) { + if (j) { + x = cx - ((cx-px)/2.0); + gapscalex += x; + x = gapscalex; + } else { + x = 0; + gapscalex = 0; + } + widthcols = 0; + } else if (interpolate_scaley) { + x = 0; + if (i == 1) { + ygap[j] = ((cy-y[j])/2.0); + y[j] += ygap[j]; + } + yset = y[j]; + y[j] += cy + ygap[j]; + heightrows = 0; + } + } + prev_bbox = bbox; + } else { + y[j] = 0; + } + if (!counter) { + counter++; + continue; + } + double xset = x; + xset += widthcols * j; + if (heightrows) { + yset = heightrows * i; + } + SPItem * item = toItem(counter - 1, reset, write); + if (item) { + if (!(lpesatellites.data().size() > counter - 1 && lpesatellites.data()[counter - 1])) { + item->deleteObject(true); + return; + } + prev_bbox = item->geometricBounds(); + (*prev_bbox) *= r; + double offset_x = 0; + double offset_y = 0; + if (offset != 0) { + if (offset_type && j%2) { + offset_y = fixed_heightrows/(100.0/(double)offset); + } + if (!offset_type && i%2) { + offset_x = fixed_widthcols/(100.0/(double)offset); + } + } + + + auto p = Geom::Point(xset + offset_x - random_x[counter], yset + offset_y - random_y[counter]); + auto translate = p * gap.inverse(); + Geom::Affine finalit = (transformoriginal * Geom::Translate(spcenter_base).inverse() * mirror * Geom::Translate(spcenter_base)); + finalit *= gapp.inverse() * Geom::Translate(spcenter).inverse() * originatrans.withoutTranslation().inverse() * r * translate * Geom::Translate(spcenter) ; + item->doWriteTransform(finalit); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + forcewrite = forcewrite || write; + } + counter++; + } + } + //we keep satellites connected and active if write needed + bool connected = lpesatellites.is_connected(); + if (forcewrite || !connected) { + lpesatellites.write_to_SVG(); + lpesatellites.start_listening(); + lpesatellites.update_satellites(!connected); + } + reset = link_styles; + } +} + +void LPETiling::cloneStyle(SPObject *orig, SPObject *dest) +{ + dest->setAttribute("transform", orig->getAttribute("transform")); + dest->setAttribute("style", orig->getAttribute("style")); + dest->setAttribute("mask", orig->getAttribute("mask")); + dest->setAttribute("clip-path", orig->getAttribute("clip-path")); + dest->setAttribute("class", orig->getAttribute("class")); + for (auto iter : orig->style->properties()) { + if (iter->style_src != SPStyleSrc::UNSET) { + auto key = iter->id(); + if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) { + const gchar *attr = orig->getAttribute(iter->name().c_str()); + if (attr) { + dest->setAttribute(iter->name(), attr); + } + } + } + } +} + +void LPETiling::cloneD(SPObject *orig, SPObject *dest) +{ + SPDocument *document = getSPDoc(); + if (!document) { + return; + } + if ( SP_IS_GROUP(orig) && SP_IS_GROUP(dest) && SP_GROUP(orig)->getItemCount() == SP_GROUP(dest)->getItemCount() ) { + if (reset) { + cloneStyle(orig, dest); + } + std::vector< SPObject * > childs = orig->childList(true); + size_t index = 0; + for (auto & child : childs) { + SPObject *dest_child = dest->nthChild(index); + cloneD(child, dest_child); + index++; + } + return; + } else if( SP_IS_GROUP(orig) && SP_IS_GROUP(dest) && SP_GROUP(orig)->getItemCount() != SP_GROUP(dest)->getItemCount()) { + split_items.param_setValue(false); + return; + } + + if ( SP_IS_TEXT(orig) && SP_IS_TEXT(dest) && SP_TEXT(orig)->children.size() == SP_TEXT(dest)->children.size()) { + if (reset) { + cloneStyle(orig, dest); + } + size_t index = 0; + for (auto & child : SP_TEXT(orig)->children) { + SPObject *dest_child = dest->nthChild(index); + cloneD(&child, dest_child); + index++; + } + } + + SPShape * shape = SP_SHAPE(orig); + SPPath * path = SP_PATH(dest); + if (shape) { + SPCurve *c = shape->curve(); + if (c) { + auto str = sp_svg_write_path(c->get_pathvector()); + if (shape && !path) { + const char *id = dest->getAttribute("id"); + const char *style = dest->getAttribute("style"); + Inkscape::XML::Document *xml_doc = dest->document->getReprDoc(); + Inkscape::XML::Node *dest_node = xml_doc->createElement("svg:path"); + dest_node->setAttribute("id", id); + dest_node->setAttribute("style", style); + dest->updateRepr(xml_doc, dest_node, SP_OBJECT_WRITE_ALL); + path = SP_PATH(dest); + } + path->setAttribute("d", str); + } else { + path->removeAttribute("d"); + } + } + if (reset) { + cloneStyle(orig, dest); + } +} + +Inkscape::XML::Node * +LPETiling::createPathBase(SPObject *elemref) { + SPDocument *document = getSPDoc(); + if (!document) { + return nullptr; + } + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *prev = elemref->getRepr(); + SPGroup *group = dynamic_cast<SPGroup *>(elemref); + if (group) { + Inkscape::XML::Node *container = xml_doc->createElement("svg:g"); + container->setAttribute("transform", prev->attribute("transform")); + container->setAttribute("mask", prev->attribute("mask")); + container->setAttribute("clip-path", prev->attribute("clip-path")); + container->setAttribute("class", prev->attribute("class")); + container->setAttribute("style", prev->attribute("style")); + std::vector<SPItem*> const item_list = sp_item_group_item_list(group); + Inkscape::XML::Node *previous = nullptr; + for (auto sub_item : item_list) { + Inkscape::XML::Node *resultnode = createPathBase(sub_item); + + container->addChild(resultnode, previous); + previous = resultnode; + } + return container; + } + Inkscape::XML::Node *resultnode = xml_doc->createElement("svg:path"); + resultnode->setAttribute("transform", prev->attribute("transform")); + resultnode->setAttribute("style", prev->attribute("style")); + resultnode->setAttribute("mask", prev->attribute("mask")); + resultnode->setAttribute("clip-path", prev->attribute("clip-path")); + resultnode->setAttribute("class", prev->attribute("class")); + return resultnode; +} + + +SPItem * +LPETiling::toItem(size_t i, bool reset, bool &write) +{ + SPDocument *document = getSPDoc(); + if (!document) { + return nullptr; + } + + SPObject *elemref = nullptr; + if (container != sp_lpe_item->parent) { + lpesatellites.read_from_SVG(); + return nullptr; + } + if (lpesatellites.data().size() > i && lpesatellites.data()[i]) { + elemref = lpesatellites.data()[i]->getObject(); + } + Inkscape::XML::Node *phantom = nullptr; + bool creation = false; + if (elemref) { + phantom = elemref->getRepr(); + } else { + creation = true; + phantom = createPathBase(sp_lpe_item); + reset = true; + elemref = container->appendChildRepr(phantom); + + Inkscape::GC::release(phantom); + } + cloneD(sp_lpe_item, elemref); + reset = link_styles; + if (creation) { + write = true; + lpesatellites.link(elemref, i); + } + return dynamic_cast<SPItem *>(elemref); +} + +Gtk::Widget * LPETiling::newWidget() +{ + // use manage here, because after deletion of Effect object, others might + // still be pointing to this widget. + Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + vbox->set_border_width(5); + vbox->set_homogeneous(false); + vbox->set_spacing(0); + Gtk::Widget *combo = nullptr; + Gtk::Widget *randbutton = nullptr; + Gtk::Box *containerstart = nullptr; + Gtk::Box *containerend = nullptr; + Gtk::Box *movestart = nullptr; + Gtk::Box *moveend = nullptr; + Gtk::Box *rowcols = nullptr; + std::vector<Parameter *>::iterator it = param_vector.begin(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool usemirroricons = prefs->getBool("/live_effects/copy/mirroricons",true); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget()); + Glib::ustring *tip = param->param_getTooltip(); + if (widg) { + if (param->param_key == "unit") { + auto widgcombo = dynamic_cast<Inkscape::UI::Widget::RegisteredUnitMenu*>(widg); + delete widgcombo->get_children()[0]; + combo = dynamic_cast<Gtk::Widget*>(widgcombo); + if (usemirroricons) { + Gtk::RadioButton::Group group; + Gtk::Frame * frame = Gtk::manage(new Gtk::Frame(_("Mirroring mode"))); + Gtk::Box * cbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,8)); + Gtk::Box * vbox1 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + Gtk::Box * hbox1 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::Box * hbox2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::Box * vbox2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + Gtk::Box * hbox3 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::Box * hbox4 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + vbox2->set_margin_start(5); + cbox->pack_start(*vbox1, false, false, 0); + cbox->pack_start(*vbox2, false, false, 0); + cbox->set_margin_start(3); + cbox->set_margin_end(3); + cbox->set_margin_bottom(3); + frame->add(*cbox); + vbox->pack_start(*frame, false, false, 1); + vbox1->pack_start(*hbox1, false, false, 0); + vbox1->pack_start(*hbox2, false, false, 0); + vbox2->pack_start(*hbox3, false, false, 0); + vbox2->pack_start(*hbox4, false, false, 0); + generate_buttons(hbox1, group, 0); + generate_buttons(hbox2, group, 1); + generate_buttons(hbox3, group, 2); + generate_buttons(hbox4, group, 3); + } + ++it; + continue; + } else if (param->param_key == "seed"){ + auto widgrand = dynamic_cast<Inkscape::UI::Widget::RegisteredRandom*>(widg); + delete widgrand->get_children()[0]; + widgrand->get_children()[0]->hide(); + widgrand->get_children()[0]->set_no_show_all(true); + auto button = dynamic_cast<Gtk::Button*>(widgrand->get_children()[1]); + button->set_always_show_image(true); + button->set_label(_("Randomize")); + button->set_tooltip_markup(_("Randomization seed for random mode for scaling, rotation and gaps")); + button->set_relief(Gtk::RELIEF_NORMAL); + button->set_image_from_icon_name(INKSCAPE_ICON("randomize"), Gtk::IconSize(Gtk::ICON_SIZE_BUTTON)); + widgrand->set_vexpand(false); + widgrand->set_hexpand(true); + widgrand->set_valign(Gtk::ALIGN_START); + widgrand->set_halign(Gtk::ALIGN_START); + randbutton = dynamic_cast<Gtk::Widget*>(Gtk::manage(widgrand)); + ++it; + continue; + } else if (param->param_key == "offset_type" || + param->param_key == "mirrorrowsx" && usemirroricons || + param->param_key == "mirrorrowsy" && usemirroricons || + param->param_key == "mirrorcolsx" && usemirroricons || + param->param_key == "mirrorcolsy" && usemirroricons || + param->param_key == "interpolate_rotatex" || + param->param_key == "interpolate_rotatey" || + param->param_key == "interpolate_scalex" || + param->param_key == "interpolate_scaley" || + param->param_key == "random_scale" || + param->param_key == "random_rotate" || + param->param_key == "random_gap_x" || + param->param_key == "random_gap_y") + { + ++it; + continue; + } else if (param->param_key == "offset") { + movestart->pack_start(*widg, false, false, 2); + /* widg->set_halign(Gtk::ALIGN_END); */ + /* widg->set_hexpand(true); */ + /* auto widgscalar = dynamic_cast<Inkscape::UI::Widget::RegisteredScalar *>(widg); + widgscalar->get_children()[0]->set_halign(Gtk::ALIGN_START); */ + Gtk::Box *container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::RadioButton::Group group; + Gtk::RadioToolButton * rows = Gtk::manage(new Gtk::RadioToolButton(group, _("Offset rows"))); + rows->set_icon_name(INKSCAPE_ICON("rows")); + rows->set_tooltip_markup(_("Offset alternate rows")); + Gtk::RadioToolButton * cols = Gtk::manage(new Gtk::RadioToolButton(group, _("Offset cols"))); + cols->set_icon_name(INKSCAPE_ICON("cols")); + cols->set_tooltip_markup(_("Offset alternate cols")); + if (offset_type) { + cols->set_active(); + } else { + rows->set_active(); + } + container->pack_start(*rows, false, false, 1); + container->pack_start(*cols, false, false, 1); + cols->signal_clicked().connect(sigc::mem_fun (*this, &LPETiling::setOffsetCols)); + rows->signal_clicked().connect(sigc::mem_fun (*this, &LPETiling::setOffsetRows)); + moveend->pack_start(*container, false, false, 2); + } else if (param->param_key == "scale") { + Gtk::Box *container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::RadioButton::Group group; + Gtk::RadioToolButton * cols = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate X"))); + cols->set_icon_name(INKSCAPE_ICON("interpolate-scale-x")); + Gtk::RadioToolButton * rows = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate Y"))); + rows->set_icon_name(INKSCAPE_ICON("interpolate-scale-y")); + Gtk::RadioToolButton * both = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate both"))); + both->set_icon_name(INKSCAPE_ICON("interpolate-scale-both")); + Gtk::RadioToolButton * none = Gtk::manage(new Gtk::RadioToolButton(group, _("No interpolation"))); + none->set_icon_name(INKSCAPE_ICON("interpolate-scale-none")); + Gtk::RadioToolButton * rand = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate random"))); + rand->set_icon_name(INKSCAPE_ICON("scale-random")); + if (interpolate_scalex && interpolate_scaley) { + both->set_active(); + } else if (interpolate_scalex) { + cols->set_active(); + } else if (interpolate_scaley) { + rows->set_active(); + } else if (random_scale) { + rand->set_active(); + } else { + none->set_active(); + } + cols->set_tooltip_markup(_("Blend scale from <b>left to right</b> (left column uses original scale, right column uses new scale)")); + rows->set_tooltip_markup(_("Blend scale from <b>top to bottom</b> (top row uses original scale, bottom row uses new scale)")); + both->set_tooltip_markup(_("Blend scale <b>diagonally</b> (top left tile uses original scale, bottom right tile uses new scale)")); + none->set_tooltip_markup(_("Uniform scale")); + rand->set_tooltip_markup(_("Random scale (hit <b>Randomize</b> button to shuffle)")); + container->pack_start(*rows, false, false, 1); + container->pack_start(*cols, false, false, 1); + container->pack_start(*both, false, false, 1); + container->pack_start(*none, false, false, 1); + container->pack_start(*rand, false, false, 1); + rand->signal_clicked().connect(sigc::mem_fun(*this, &LPETiling::setScaleRandom)); + none->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), false, false)); + cols->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), true, false)); + rows->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), false, true)); + both->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), true, true)); + movestart->pack_start(*widg, false, false, 2); + moveend->pack_start(*container, false, false, 2); + } else if (param->param_key == "rotate") { + movestart->pack_start(*widg, false, false, 2); + Gtk::Box *container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::RadioButton::Group group; + Gtk::RadioToolButton * cols = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate X"))); + cols->set_icon_name(INKSCAPE_ICON("interpolate-rotate-x")); + Gtk::RadioToolButton * rows = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate Y"))); + rows->set_icon_name(INKSCAPE_ICON("interpolate-rotate-y")); + Gtk::RadioToolButton * both = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate both"))); + both->set_icon_name(INKSCAPE_ICON("interpolate-rotate-both")); + Gtk::RadioToolButton * none = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate none"))); + none->set_icon_name(INKSCAPE_ICON("interpolate-rotate-none")); + Gtk::RadioToolButton * rand = Gtk::manage(new Gtk::RadioToolButton(group, _("Interpolate random"))); + rand->set_icon_name(INKSCAPE_ICON("rotate-random")); + if (interpolate_rotatex && interpolate_rotatey) { + both->set_active(); + } else if (interpolate_rotatex) { + cols->set_active(); + } else if (interpolate_rotatey) { + rows->set_active(); + } else if (random_rotate) { + rand->set_active(); + } else { + none->set_active(); + } + cols->set_tooltip_markup(_("Blend rotation from <b>left to right</b> (left column uses original rotation, right column uses new rotation)")); + rows->set_tooltip_markup(_("Blend rotation from <b>top to bottom</b> (top row uses original rotation, bottom row uses new rotation)")); + both->set_tooltip_markup(_("Blend rotation <b>diagonally</b> (top left tile uses original rotation, bottom right tile uses new rotation)")); + none->set_tooltip_markup(_("Uniform rotation")); + rand->set_tooltip_markup(_("Random rotation (hit <b>Randomize</b> button to shuffle)")); + container->pack_start(*rows, false, false, 1); + container->pack_start(*cols, false, false, 1); + container->pack_start(*both, false, false, 1); + container->pack_start(*none, false, false, 1); + container->pack_start(*rand, false, false, 1); + rand->signal_clicked().connect(sigc::mem_fun(*this, &LPETiling::setRotateRandom)); + none->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), false, false)); + cols->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), true, false)); + rows->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), false, true)); + both->signal_clicked().connect(sigc::bind<bool,bool>(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), true, true)); + moveend->pack_start(*container, false, false, 2); + } else if (param->param_key == "gapx") { + Gtk::Box *wrapper = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + movestart = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + moveend = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + Gtk::Box *container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::RadioButton::Group group; + Gtk::RadioToolButton * normal = Gtk::manage(new Gtk::RadioToolButton(group, _("Normal"))); + normal->set_icon_name(INKSCAPE_ICON("interpolate-scale-none")); // same icon + Gtk::RadioToolButton * randx = Gtk::manage(new Gtk::RadioToolButton(group, _("Random"))); + randx->set_icon_name(INKSCAPE_ICON("gap-random-x")); + if (random_gap_x) { + randx->set_active(); + } else { + normal->set_active(); + } + normal->set_tooltip_markup(_("All horizontal gaps have the same width")); + randx->set_tooltip_markup(_("Random horizontal gaps (hit <b>Randomize</b> button to shuffle)")); + normal->signal_clicked().connect(sigc::bind<bool>(sigc::mem_fun(*this, &LPETiling::setGapXMode), false)); + randx->signal_clicked().connect(sigc::bind<bool>(sigc::mem_fun(*this, &LPETiling::setGapXMode), true)); + container->pack_start(*normal, false, false, 1); + container->pack_start(*randx, false, false, 1); + container->pack_end(*combo, false, false, 1); + movestart->pack_start(*widg, false, false, 2); + moveend->pack_start(*container, true, true, 2); + wrapper->pack_start(*movestart, false, false, 0); + wrapper->pack_start(*moveend, false, false, 0); + //bwidg->set_hexpand(true); + combo->set_halign(Gtk::ALIGN_END); + widg->set_halign(Gtk::ALIGN_START); + vbox->pack_start(*wrapper, true, true, 0); + } else if (param->param_key == "gapy") { + movestart->pack_start(*widg, true, true, 2); + Gtk::Box *container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::RadioButton::Group group; + Gtk::RadioToolButton * normal = Gtk::manage(new Gtk::RadioToolButton(group, _("Normal"))); + normal->set_icon_name(INKSCAPE_ICON("interpolate-scale-none")); // same icon + Gtk::RadioToolButton * randy = Gtk::manage(new Gtk::RadioToolButton(group, _("Random"))); + randy->set_icon_name(INKSCAPE_ICON("gap-random-y")); + if (random_gap_y) { + randy->set_active(); + } else { + normal->set_active(); + } + normal->set_tooltip_markup(_("All vertical gaps have the same height")); + randy->set_tooltip_markup(_("Random vertical gaps (hit <b>Randomize</b> button to shuffle)")); + normal->signal_clicked().connect(sigc::bind<bool>(sigc::mem_fun(*this, &LPETiling::setGapYMode), false)); + randy->signal_clicked().connect(sigc::bind<bool>(sigc::mem_fun(*this, &LPETiling::setGapYMode), true)); + container->pack_start(*normal, false, false, 1); + container->pack_start(*randy, false, false, 1); + widg->set_halign(Gtk::ALIGN_START); + moveend->pack_start(*container, false, false, 2); + } else if (param->param_key == "mirrortrans"){ + Gtk::Box *container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + Gtk::Box *containerwraper = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + containerend = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + containerstart = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + container->pack_start(*containerwraper, true, true, 0); + containerwraper->pack_start(*containerstart, true, true, 0); + containerwraper->pack_start(*containerend, true, true, 0); + containerend->pack_end(*randbutton, true, true, 2); + containerstart->pack_start(*widg, true, true, 2); + container->set_hexpand(true); + containerwraper->set_hexpand(true); + containerend->set_hexpand(true); + containerstart->set_hexpand(true); + vbox->pack_start(*container, true, true, 1); + } else if ( + param->param_key == "split_items" || + param->param_key == "link_styles" || + param->param_key == "shrink_interp") + { + containerstart->pack_start(*widg, true, true, 2); + widg->set_vexpand(false); + widg->set_hexpand(false); + widg->set_valign(Gtk::ALIGN_START); + widg->set_halign(Gtk::ALIGN_START); + } else if (param->param_key == "num_rows") { + rowcols = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + rowcols->pack_start(*widg, false, false, 2); + vbox->pack_start(*rowcols, true, true, 2); + } else if (param->param_key == "num_cols") { + rowcols->pack_start(*widg, false, false, 2); + } else { + vbox->pack_start(*widg, true, true, 2); + } + if (tip) { + widg->set_tooltip_markup(*tip); + } else { + widg->set_tooltip_markup(""); + widg->set_has_tooltip(false); + } + } + } + + ++it; + } + if(Gtk::Widget* widg = defaultParamSet()) { + vbox->pack_start(*widg, true, true, 2); + } + return dynamic_cast<Gtk::Widget *>(vbox); +} + +void +LPETiling::generate_buttons(Gtk::Box *container, Gtk::RadioButton::Group &group, gint pos) +{ + for (int i = 0; i < 4; i++) { + gint position = (pos * 4) + i; + Glib::ustring result = getMirrorMap(position); + Gtk::RadioToolButton * button = Gtk::manage(new Gtk::RadioToolButton(group)); + Glib::ustring iconname = "mirroring"; + iconname += "-"; + iconname += result; + button->set_icon_name(INKSCAPE_ICON(iconname)); + if (getActiveMirror(position)) { + _updating = true; + button->set_active(); + _updating = false; + } + button->signal_clicked().connect(sigc::bind<gint>(sigc::mem_fun(*this, &LPETiling::setMirroring),position)); + gint zero = Glib::ustring("0")[0]; + Glib::ustring tooltip = result[0] == zero ? "" : "rx+"; + tooltip += result[1] == zero ? "" : "ry+"; + tooltip += result[2] == zero ? "" : "cx+"; + tooltip += result[3] == zero ? "" : "cy+"; + if (tooltip.size()) { + tooltip.erase(tooltip.size()-1); + } + button->set_tooltip_markup(tooltip); + button->set_margin_start(1); + container->pack_start(*button, false, false, 1); + } +} + +Glib::ustring +LPETiling::getMirrorMap(gint index) +{ + Glib::ustring result = "0000"; + if (index == 1) { + result = "1000"; + } else if (index == 2) { + result = "1100"; + } else if (index == 3) { + result = "0100"; + } else if (index == 4) { + result = "0011"; + } else if (index == 5) { + result = "1011"; + } else if (index == 6) { + result = "1111"; + } else if (index == 7) { + result = "0111"; + } else if (index == 8) { + result = "0010"; + } else if (index == 9) { + result = "1010"; + } else if (index == 10) { + result = "1110"; + } else if (index == 11) { + result = "0110"; + } else if (index == 12) { + result = "0001"; + } else if (index == 13) { + result = "1001"; + } else if (index == 14) { + result = "1101"; + } else if (index == 15) { + result = "0101"; + } + return result; +} + +bool +LPETiling::getActiveMirror(gint index) +{ + Glib::ustring result = getMirrorMap(index); + return result[0] == Glib::ustring::format(mirrorrowsx)[0] && + result[1] == Glib::ustring::format(mirrorrowsy)[0] && + result[2] == Glib::ustring::format(mirrorcolsx)[0] && + result[3] == Glib::ustring::format(mirrorcolsy)[0]; +} + +void +LPETiling::setMirroring(gint index) +{ + if (_updating) { + return; + } + _updating = true; + Glib::ustring result = getMirrorMap(index); + gint zero = Glib::ustring("0")[0]; + mirrorrowsx.param_setValue(result[0] == zero ? false : true); + mirrorrowsy.param_setValue(result[1] == zero ? false : true); + mirrorcolsx.param_setValue(result[2] == zero ? false : true); + mirrorcolsy.param_setValue(result[3] == zero ? false : true); + writeParamsToSVG(); + _updating = false; +} + +void +LPETiling::setOffsetCols(){ + offset_type.param_setValue(true); + offset_type.write_to_SVG(); +} +void +LPETiling::setOffsetRows(){ + offset_type.param_setValue(false); + offset_type.write_to_SVG(); +} + +void +LPETiling::setRotateInterpolate(bool x, bool y){ + interpolate_rotatex.param_setValue(x); + interpolate_rotatey.param_setValue(y); + random_rotate.param_setValue(false); + writeParamsToSVG(); +} + +void +LPETiling::setScaleInterpolate(bool x, bool y){ + interpolate_scalex.param_setValue(x); + interpolate_scaley.param_setValue(y); + random_scale.param_setValue(false); + writeParamsToSVG(); +} + +void +LPETiling::setRotateRandom() { + interpolate_rotatex.param_setValue(false); + interpolate_rotatey.param_setValue(false); + random_rotate.param_setValue(true); + writeParamsToSVG(); +} + +void +LPETiling::setScaleRandom() { + interpolate_scalex.param_setValue(false); + interpolate_scaley.param_setValue(false); + random_scale.param_setValue(true); + writeParamsToSVG(); +} + +void +LPETiling::setGapXMode(bool random) { + random_gap_x.param_setValue(random); + writeParamsToSVG(); +} + +void +LPETiling::setGapYMode(bool random) { + random_gap_y.param_setValue(random); + writeParamsToSVG(); +} + +void +LPETiling::doOnApply(SPLPEItem const* lpeitem) +{ + if (lpeitem->getAttribute("transform")) { + transformorigin.param_setValue(lpeitem->getAttribute("transform"), true); + } else { + transformorigin.param_setValue("", true); + } + doBeforeEffect(lpeitem); +} + +void +LPETiling::doBeforeEffect (SPLPEItem const* lpeitem) +{ + auto transformorigin_str = lpeitem->getAttribute("transform"); + if (transformorigin_str) { + transformorigin.read_from_SVG(); + auto transformorigin_str = transformorigin.param_getSVGValue(); + transformoriginal = Geom::identity(); + if (!transformorigin_str.empty()) { + sp_svg_transform_read(transformorigin_str.c_str(), &transformoriginal); + } + } else { + transformorigin.param_setValue("", true); + transformoriginal = Geom::identity(); + } + //transformoriginal = transformoriginal.withoutTranslation(); + using namespace Geom; + seed.resetRandomizer(); + random_x.clear(); + random_y.clear(); + random_s.clear(); + random_r.clear(); + if (prev_unit != unit.get_abbreviation()) { + double newgapx = Inkscape::Util::Quantity::convert(gapx, prev_unit, unit.get_abbreviation()); + double newgapy = Inkscape::Util::Quantity::convert(gapy, prev_unit, unit.get_abbreviation()); + gapx.param_set_value(newgapx); + gapy.param_set_value(newgapy); + prev_unit = unit.get_abbreviation(); + writeParamsToSVG(); + } + scaleok = (scale + 100) / 100.0; + double seedset = seed.param_get_random_number() - seed.param_get_random_number(); + affinebase = Geom::identity(); + if (random_rotate && rotate) { + affinebase *= Geom::Rotate::from_degrees(seedset * rotate); + } + if (random_scale && scaleok != 1) { + affinebase *= Geom::Scale(seed.param_get_random_number() * (std::max(scaleok,1.0) - std::min(scaleok,1.0)) + std::min(scaleok,1.0)); + } + if (random_gap_x && gapx_unit) { + affinebase *= Geom::Translate(seed.param_get_random_number() * gapx_unit * -1, 0); + } + if (random_gap_y && gapy_unit) { + affinebase *= Geom::Translate(0,seed.param_get_random_number() * gapy_unit * -1); + } + if (!split_items && lpesatellites.data().size()) { + processObjects(LPE_ERASE); + } + if (link_styles) { + reset = true; + } + if (split_items && !lpesatellites.data().size()) { + lpesatellites.read_from_SVG(); + if (lpesatellites.data().size()) { + lpesatellites.update_satellites(); + } + } + Glib::ustring display_unit = lpeitem->document->getDisplayUnit()->abbr.c_str(); + gapx_unit = Inkscape::Util::Quantity::convert(gapx, unit.get_abbreviation(), display_unit.c_str()); + gapy_unit = Inkscape::Util::Quantity::convert(gapy, unit.get_abbreviation(), display_unit.c_str()); + original_bbox(sp_lpe_item, false, true, transformoriginal); + originalbbox = Geom::OptRect(boundingbox_X,boundingbox_Y); + Geom::Point A = Point(boundingbox_X.min() - (gapx_unit / 2.0), boundingbox_Y.min() - (gapy_unit / 2.0)); + Geom::Point B = Point(boundingbox_X.max() + (gapx_unit / 2.0), boundingbox_Y.max() + (gapy_unit / 2.0)); + gap_bbox = Geom::OptRect(A,B); + if (!gap_bbox) { + return; + } + + double scale_fix = end_scale(scaleok, true); + (*originalbbox) *= Geom::Translate((*originalbbox).midpoint()).inverse() * Geom::Scale(scale_fix) * Geom::Translate((*originalbbox).midpoint()); + if (!interpolate_scalex && !interpolate_scaley && !random_scale) { + (*gap_bbox) *= Geom::Translate((*gap_bbox).midpoint()).inverse() * Geom::Scale(scaleok,scaleok) * Geom::Translate((*gap_bbox).midpoint()); + (*originalbbox) *= Geom::Translate((*originalbbox).midpoint()).inverse() * Geom::Scale(scaleok,scaleok) * Geom::Translate((*originalbbox).midpoint()); + } + original_width = (*gap_bbox).width(); + original_height = (*gap_bbox).height(); +} + +double +LPETiling::end_scale(double scale_fix, bool tomax) const { + if (interpolate_scalex && interpolate_scaley) { + scale_fix = 1 + ((scale_fix - 1) * (num_rows + num_cols -1)); + } else if (interpolate_scalex) { + scale_fix = 1 + ((scale_fix - 1) * (num_cols -1)); + } else if (interpolate_scaley) { + scale_fix = 1 + ((scale_fix - 1) * (num_rows -1)); + } + if (tomax && (random_scale || interpolate_scalex || interpolate_scaley)) { + scale_fix = std::max(scale_fix, 1.0); + } + return scale_fix; +} + +Geom::PathVector +LPETiling::doEffect_path (Geom::PathVector const & path_in) +{ + Geom::PathVector path_out; + FillRuleBool fillrule = fill_nonZero; + if (current_shape->style && + current_shape->style->fill_rule.set && + current_shape->style->fill_rule.computed == SP_WIND_RULE_EVENODD) + { + fillrule = (FillRuleBool)fill_oddEven; + } + path_out = doEffect_path_post(path_in, fillrule); + if (_knotholder) { + _knotholder->update_knots(); + } + if (split_items) { + return path_out; + } else { + return path_out * transformoriginal.inverse(); + } +} + +Geom::PathVector +LPETiling::doEffect_path_post (Geom::PathVector const & path_in, FillRuleBool fillrule) +{ + if (!gap_bbox) { + return path_in; + } + Geom::Point spcenter_base = (*sp_lpe_item->geometricBounds(transformoriginal)).midpoint(); + Geom::Point center = (*gap_bbox).midpoint() * transformoriginal.inverse(); + Geom::PathVector output; + gint counter = 0; + Geom::OptRect prev_bbox; + double gapscalex = 0; + double maxheight = 0; + double maxwidth = 0; + double minheight = std::numeric_limits<double>::max(); + Geom::OptRect bbox = path_in.boundsFast(); + if (!bbox) { + return path_in; + } + (*bbox) *= transformoriginal; + + double posx = ((*gap_bbox).left() - (*bbox).left()) / (*gap_bbox).width(); + double factorx = original_width/(*bbox).width(); + double factory = original_height/(*bbox).height(); + double y[(int)num_cols]; + double yset = 0; + double gap[(int)num_cols]; + for (int i = 0; i < num_rows; ++i) { + double fracy = 1; + if (num_rows != 1) { + fracy = i/(double)(num_rows - 1); + } + for (int j = 0; j < num_cols; ++j) { + double x = 0; + double fracx = 1; + if (num_cols != 1) { + fracx = j/(double)(num_cols - 1); + } + Geom::Affine r = Geom::identity(); + r = Geom::identity(); + Geom::Scale mirror = Geom::Scale(1,1); + if(mirrorrowsx || mirrorrowsy || mirrorcolsx || mirrorcolsy) { + gint mx = 1; + gint my = 1; + if (mirrorrowsx && mirrorcolsx) { + mx = (j+i)%2 != 0 ? -1 : 1; + } else { + if (mirrorrowsx) { + mx = i%2 != 0 ? -1 : 1; + } else if (mirrorcolsx) { + mx = j%2 != 0 ? -1 : 1; + } + } + if (mirrorrowsy && mirrorcolsy) { + my = (j+i)%2 != 0 ? -1 : 1; + } else { + if (mirrorrowsy) { + my = i%2 != 0 ? -1 : 1; + } else if (mirrorcolsy) { + my = j%2 != 0 ? -1 : 1; + } + } + mirror = Geom::Scale(mx, my); + } + if (mirrortrans && interpolate_scalex && i%2 != 0) { + fracx = 1-fracx; + } + double fracyin = fracy; + if (mirrortrans && interpolate_scaley && j%2 != 0) { + fracyin = 1-fracyin; + } + /* if (mirrortrans && interpolate_scaley && interpolate_scalex) { + fract = 1-fract; + } */ + double rotatein = rotate; + if (interpolate_rotatex && interpolate_rotatey) { + rotatein = rotatein * (i + j); + } else if (interpolate_rotatex) { + rotatein = rotatein * j; + } else if (interpolate_rotatey) { + rotatein = rotatein * i; + } + if (mirrortrans && + ((interpolate_rotatex && i%2 != 0) || + (interpolate_rotatey && j%2 != 0) || + (interpolate_rotatex && interpolate_rotatey))) + { + rotatein *=-1; + } + double scalein = 1; + double scalegap = scaleok - scalein; + if (interpolate_scalex && interpolate_scaley) { + scalein = (scalegap * (i + j)) + 1; + } else if (interpolate_scalex) { + scalein = (scalegap * j) + 1; + } else if (interpolate_scaley) { + scalein = (scalegap * i) + 1; + } else { + scalein = scaleok; + } + + if (random_scale && scaleok != 1.0) { + if (random_s.size() == counter) { + double max = std::max(1.0,scaleok); + double min = std::min(1.0,scaleok); + random_s.emplace_back(seed.param_get_random_number() * (max - min) + min); + } + scalein = random_s[counter]; + } + if (random_rotate && rotate) { + if (random_r.size() == counter) { + random_r.emplace_back((seed.param_get_random_number() - seed.param_get_random_number()) * rotate); + } + rotatein = random_r[counter]; + } + if (random_x.size() == counter) { + if (random_gap_x && gapx_unit && (j || i)) { + random_x.emplace_back((seed.param_get_random_number() * gapx_unit)); // avoid overlapping + } else { + random_x.emplace_back(0); + } + } + if (random_y.size() == counter) { + if (random_gap_y && gapy_unit && (j || i)) { + random_y.emplace_back((seed.param_get_random_number() * gapy_unit)); // avoid overlapping + } else { + random_y.emplace_back(0); + } + } + r *= Geom::Scale(scalein, scalein); + r *= Geom::Rotate::from_degrees(rotatein); + + Geom::PathVector output_pv = pathv_to_linear_and_cubic_beziers(path_in); + + output_pv *= Geom::Translate(center).inverse(); + output_pv *= r; + if (!interpolate_rotatex && !interpolate_rotatey && !random_rotate) { + output_pv *= Geom::Rotate::from_degrees(rotate); + } + if (!interpolate_scalex && !interpolate_scaley && !random_scale) { + output_pv *= Geom::Scale(scaleok, scaleok); + } + originatrans = r; + output_pv *= Geom::Translate(center); + if (split_items) { + return output_pv; + } + double scale_fix = end_scale(scaleok, true); + double heightrows = original_height * scale_fix; + double widthcols = original_width * scale_fix; + double fixed_heightrows = heightrows; + double fixed_widthcols = widthcols; + + if (rotatein && shrink_interp) { + shrink_interp.param_setValue(false); + shrink_interp.write_to_SVG(); + return path_in; + } + if (scaleok != 1.0 && (interpolate_scalex || interpolate_scaley )) { + Geom::OptRect bbox = output_pv.boundsFast(); + if (bbox) { + maxheight = std::max(maxheight,(*bbox).height()); + maxwidth = std::max(maxwidth,(*bbox).width()); + minheight = std::min(minheight,(*bbox).height()); + widthcols = std::max(original_width * end_scale(scaleok, false),original_width); + heightrows = std::max(original_height * end_scale(scaleok, false),original_height); + fixed_widthcols = widthcols; + fixed_heightrows = heightrows; + double cx = (*bbox).width(); + double cy = (*bbox).height(); + if (shrink_interp && (!interpolate_scalex || !interpolate_scaley)) { + heightrows = 0; + widthcols = 0; + double px = 0; + double py = 0; + if (prev_bbox) { + px = (*prev_bbox).width(); + py = (*prev_bbox).height(); + } + if (interpolate_scalex) { + if (j) { + x = ((cx - ((cx - px) / 2.0))) * factorx; + gapscalex += x; + x = gapscalex; + } else { + x = 0; + gapscalex = 0; + } + } else { + x = (std::max(original_width * end_scale(scaleok, false), original_width) + posx) * j; + } + if (interpolate_scalex && i == 1) { + y[j] = maxheight * factory; + } else if(i == 0) { + y[j] = 0; + } + if (i == 1 && !interpolate_scalex) { + gap[j] = ((cy * factory) - y[j])/2.0; + } else if (i == 0) { + gap[j] = 0; + } + yset = y[j] + (gap[j] * i); + if (interpolate_scaley) { + y[j] += cy * factory; + } else { + y[j] += maxheight * factory; + } + } + prev_bbox = bbox; + } + } else { + y[j] = 0; + } + double xset = x; + xset += widthcols * j; + if (heightrows) { + yset = heightrows * i; + } + double offset_x = 0; + double offset_y = 0; + if (offset != 0) { + if (offset_type && j%2) { + offset_y = fixed_heightrows/(100.0/(double)offset); + } + if (!offset_type && i%2) { + offset_x = fixed_widthcols/(100.0/(double)offset); + + } + } + output_pv *= Geom::Translate(center).inverse() * mirror * Geom::Translate(center); + output_pv *= transformoriginal; + output_pv *= Geom::Translate(Geom::Point(xset + offset_x - random_x[counter],yset + offset_y - random_y[counter])); + output.insert(output.end(), output_pv.begin(), output_pv.end()); + counter++; + } + } + return output; +} + +void +LPETiling::addCanvasIndicators(SPLPEItem const *lpeitem, std::vector<Geom::PathVector> &hp_vec) +{ + if (!gap_bbox) { + return; + } + using namespace Geom; + hp_vec.clear(); + Geom::Path hp = Geom::Path(*gap_bbox); + double scale_fix = end_scale(scaleok, true); + hp *= Geom::Translate((*gap_bbox).midpoint()).inverse() * Geom::Scale(scale_fix) * Geom::Translate((*gap_bbox).midpoint()); + hp *= transformoriginal.inverse(); + Geom::PathVector pathv; + pathv.push_back(hp); + hp_vec.push_back(pathv); +} + +void +LPETiling::resetDefaults(SPItem const* item) +{ + Effect::resetDefaults(item); + original_bbox(SP_LPE_ITEM(item), false, true); +} + +void +LPETiling::doOnVisibilityToggled(SPLPEItem const* lpeitem) +{ + auto transformorigin_str = lpeitem->getAttribute("transform"); + Geom::Affine ontoggle = Geom::identity(); + if (transformorigin_str) { + sp_svg_transform_read(transformorigin_str, &ontoggle); + } + ontoggle = ontoggle; + if (is_visible) { + if ( ontoggle == Geom::identity()) { + transformorigin.param_setValue("", true); + } else { + ontoggle = ontoggle * hideaffine.inverse() * transformoriginal; + transformorigin.param_setValue(sp_svg_transform_write(ontoggle), true); + } + } else { + hideaffine = ontoggle; + } + processObjects(LPE_VISIBILITY); +} + +void +LPETiling::doOnRemove (SPLPEItem const* lpeitem) +{ + if (keep_paths) { + processObjects(LPE_TO_OBJECTS); + return; + } + processObjects(LPE_ERASE); +} + +void LPETiling::addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) +{ + _knotholder = knotholder; + KnotHolderEntity *e = new CoS::KnotHolderEntityCopyGapX(this); + e->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:CopiesGapX", + _("<b>Horizontal gaps between tiles</b>: drag to adjust, <b>Shift+click</b> to reset")); + knotholder->add(e); + + KnotHolderEntity *f = new CoS::KnotHolderEntityCopyGapY(this); + f->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:CopiesGapY", + _("<b>Vertical gaps between tiles</b>: drag to adjust, <b>Shift+click</b> to reset")); + knotholder->add(f); +} + +namespace CoS { + +KnotHolderEntityCopyGapX::~KnotHolderEntityCopyGapX() +{ + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + if (lpe) { + lpe->_knotholder = nullptr; + } +} + +KnotHolderEntityCopyGapY::~KnotHolderEntityCopyGapY() +{ + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + if (lpe) { + lpe->_knotholder = nullptr; + } +} + +void KnotHolderEntityCopyGapX::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) +{ + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + lpe->refresh_widgets = true; + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +void KnotHolderEntityCopyGapY::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) +{ + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + lpe->refresh_widgets = true; + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +void KnotHolderEntityCopyGapX::knot_click(guint state) +{ + if (!(state & GDK_SHIFT_MASK)) { + return; + } + + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + + lpe->gapx.param_set_value(0); + startpos = 0; + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +void KnotHolderEntityCopyGapY::knot_click(guint state) +{ + if (!(state & GDK_SHIFT_MASK)) { + return; + } + + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + + lpe->gapy.param_set_value(0); + startpos = 0; + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +void KnotHolderEntityCopyGapX::knot_set(Geom::Point const &p, Geom::Point const&/*origin*/, guint state) +{ + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + + Geom::Point const s = snap_knot_position(p, state); + if (lpe->originalbbox) { + Geom::Point point = (*lpe->originalbbox).corner(1); + point *= lpe->transformoriginal.inverse(); + double value = s[Geom::X] - point[Geom::X]; + Glib::ustring display_unit = SP_ACTIVE_DOCUMENT->getDisplayUnit()->abbr.c_str(); + value = Inkscape::Util::Quantity::convert((value/lpe->end_scale(lpe->scaleok, false)) * 2, display_unit.c_str(),lpe->unit.get_abbreviation()); + lpe->gapx.param_set_value(value); + lpe->gapx.write_to_SVG(); + } +} + +void KnotHolderEntityCopyGapY::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state) +{ + LPETiling* lpe = dynamic_cast<LPETiling *>(_effect); + + Geom::Point const s = snap_knot_position(p, state); + if (lpe->originalbbox) { + Geom::Point point = (*lpe->originalbbox).corner(3); + point *= lpe->transformoriginal.inverse(); + double value = s[Geom::Y] - point[Geom::Y]; + Glib::ustring display_unit = SP_ACTIVE_DOCUMENT->getDisplayUnit()->abbr.c_str(); + value = Inkscape::Util::Quantity::convert((value/lpe->end_scale(lpe->scaleok, false)) * 2, display_unit.c_str(),lpe->unit.get_abbreviation()); + lpe->gapy.param_set_value(value); + lpe->gapy.write_to_SVG(); + } +} + +Geom::Point KnotHolderEntityCopyGapX::knot_get() const +{ + LPETiling const * lpe = dynamic_cast<LPETiling const*> (_effect); + Geom::Point ret = Geom::Point(Geom::infinity(),Geom::infinity()); + if (lpe->originalbbox) { + auto bbox = (*lpe->originalbbox); + Glib::ustring display_unit = SP_ACTIVE_DOCUMENT->getDisplayUnit()->abbr.c_str(); + double value = Inkscape::Util::Quantity::convert(lpe->gapx, lpe->unit.get_abbreviation(), display_unit.c_str()); + double scale = lpe->scaleok; + ret = (bbox).corner(1) + Geom::Point((value * lpe->end_scale(scale, false))/2.0,0); + ret *= lpe->transformoriginal.inverse(); + } + return ret; +} + +Geom::Point KnotHolderEntityCopyGapY::knot_get() const +{ + LPETiling const * lpe = dynamic_cast<LPETiling const*> (_effect); + Geom::Point ret = Geom::Point(Geom::infinity(),Geom::infinity()); + if (lpe->originalbbox) { + auto bbox = (*lpe->originalbbox); + Glib::ustring display_unit = SP_ACTIVE_DOCUMENT->getDisplayUnit()->abbr.c_str(); + double value = Inkscape::Util::Quantity::convert(lpe->gapy, lpe->unit.get_abbreviation(), display_unit.c_str()); + double scale = lpe->scaleok; + ret = (bbox).corner(3) + Geom::Point(0,(value * lpe->end_scale(scale, false))/2.0); + ret *= lpe->transformoriginal.inverse(); + } + return ret; +} + +} // namespace CoS +} // namespace LivePathEffect +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-gaps:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : |