diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/display/control/canvas-item-ctrl.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream/1.2.2.tar.xz inkscape-upstream/1.2.2.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/display/control/canvas-item-ctrl.cpp')
-rw-r--r-- | src/display/control/canvas-item-ctrl.cpp | 1221 |
1 files changed, 1221 insertions, 0 deletions
diff --git a/src/display/control/canvas-item-ctrl.cpp b/src/display/control/canvas-item-ctrl.cpp new file mode 100644 index 0000000..f2696c8 --- /dev/null +++ b/src/display/control/canvas-item-ctrl.cpp @@ -0,0 +1,1221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * A class to represent a control node. + */ + +/* + * Author: + * Tavmjong Bah + * + * Copyright (C) 2020 Tavmjong Bah + * + * Rewrite of SPCtrl + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <2geom/transforms.h> + +#include "canvas-item-ctrl.h" + +#include "preferences.h" // Default size. +#include "display/cairo-utils.h" // argb32_from_rgba() + +#include "ui/widget/canvas.h" + +namespace Inkscape { + +CanvasItemCtrl::~CanvasItemCtrl() +{ + delete[] _cache; +} + +/** + * Create an null control node. + */ +CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group) + : CanvasItem(group) +{ + _name = "CanvasItemCtrl:Null"; + _pickable = true; // Everybody gets events from this class! +} + +/** + * Create a control ctrl. Shape auto-set by type. + */ +CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlType type) + : CanvasItem(group) + , _type(type) +{ + _name = "CanvasItemCtrl:Type_" + std::to_string(_type); + _pickable = true; // Everybody gets events from this class! + + // Use _type to set default values: + set_shape_default(); + set_size_default(); +} + +/** + * Create a control ctrl. Point is in document coordinates. + */ +CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlType type, Geom::Point const &p) + : CanvasItemCtrl(group, type) +{ + _position = p; +} + + +/** + * Create a control ctrl. + */ +CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlShape shape) + : CanvasItem(group) + , _shape(shape) + , _type(CANVAS_ITEM_CTRL_TYPE_DEFAULT) +{ + _name = "CanvasItemCtrl:Shape_" + std::to_string(_shape); + _pickable = true; // Everybody gets events from this class! +} + + +/** + * Create a control ctrl. Point is in document coordinates. + */ +CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlShape shape, Geom::Point const &p) + : CanvasItemCtrl(group, shape) +{ + _position = p; + request_update(); +} + +/** + * Set the position. Point is in document coordinates. + */ +void CanvasItemCtrl::set_position(Geom::Point const &position) +{ + // std::cout << "CanvasItemCtrl::set_ctrl: " << _name << ": " << position << std::endl; + if (_position != position) { + _position = position; + request_update(); + } +} + +/** + * Returns distance between point in canvas units and position of ctrl. + */ +double CanvasItemCtrl::closest_distance_to(Geom::Point const &p) +{ + // TODO: Different criteria for different shapes. + Geom::Point position = _position * _affine; + return Geom::distance(p, position); +} + +/** + * If tolerance is zero, returns true if point p (in canvas units) is inside bounding box, + * else returns true if p (in canvas units) is within tolerance (canvas units) distance of ctrl. + * The latter assumes ctrl center anchored. + */ +bool CanvasItemCtrl::contains(Geom::Point const &p, double tolerance) +{ + // TODO: Different criteria for different shapes. + if (tolerance == 0) { + return _bounds.interiorContains(p); + } else { + return closest_distance_to(p) <= tolerance; + } +} + +/** + * Update and redraw control ctrl. + */ +void CanvasItemCtrl::update(Geom::Affine const &affine) +{ + if (_affine == affine && !_need_update) { + // Nothing to do. + return; + } + + // Queue redraw of old area (erase previous content). + request_redraw(); + + // Get new bounds + _affine = affine; + + // We must be pixel aligned, width and height are always odd. + _bounds = Geom::Rect::from_xywh(-(_width/2.0 - 0.5), -(_height/2.0 - 0.5), _width, _height); + + // Adjust for anchor + int w_half = _width/2; + int h_half = _height/2; + int dx = 0; + int dy = 0; + + switch (_shape) { + case CANVAS_ITEM_CTRL_SHAPE_DARROW: + case CANVAS_ITEM_CTRL_SHAPE_SARROW: + case CANVAS_ITEM_CTRL_SHAPE_CARROW: + case CANVAS_ITEM_CTRL_SHAPE_SALIGN: + case CANVAS_ITEM_CTRL_SHAPE_CALIGN: + { + + _angle = _anchor * M_PI/4.0 + std::atan2(_affine[1], _affine[0]); + + double half = _width/2.0; + + dx = - (half + 2) * cos(_angle); // Add a bit to prevent tip from overlapping due to rounding errors. + dy = - (half + 2) * sin(_angle); + + switch (_shape) { + + case CANVAS_ITEM_CTRL_SHAPE_CARROW: + _angle += 5 * M_PI/4.0; + break; + + case CANVAS_ITEM_CTRL_SHAPE_SARROW: + _angle += M_PI/2.0; + break; + + case CANVAS_ITEM_CTRL_SHAPE_SALIGN: + dx = - (half/2 + 2) * cos(_angle); + dy = - (half/2 + 2) * sin(_angle); + _angle -= M_PI/2.0; + break; + + case CANVAS_ITEM_CTRL_SHAPE_CALIGN: + _angle -= M_PI/4.0; + dx = (half/2 + 2) * ( sin(_angle) - cos(_angle)); + dy = (half/2 + 2) * (-sin(_angle) - cos(_angle)); + break; + + default: + break; + } + + _built = false; // Angle may have change, must rebuild! + + break; + } + + case CANVAS_ITEM_CTRL_SHAPE_PIVOT: + case CANVAS_ITEM_CTRL_SHAPE_MALIGN: + + _angle = std::atan2(_affine[1], _affine[0]); + + _built = false; // Angle may have change, must rebuild! + + break; + + default: + + switch (_anchor) { + case SP_ANCHOR_N: + case SP_ANCHOR_CENTER: + case SP_ANCHOR_S: + break; + + case SP_ANCHOR_NW: + case SP_ANCHOR_W: + case SP_ANCHOR_SW: + dx = w_half; + break; + + case SP_ANCHOR_NE: + case SP_ANCHOR_E: + case SP_ANCHOR_SE: + dx = -w_half; + break; + } + + switch (_anchor) { + case SP_ANCHOR_W: + case SP_ANCHOR_CENTER: + case SP_ANCHOR_E: + break; + + case SP_ANCHOR_NW: + case SP_ANCHOR_N: + case SP_ANCHOR_NE: + dy = h_half; + break; + + case SP_ANCHOR_SW: + case SP_ANCHOR_S: + case SP_ANCHOR_SE: + dy = -h_half; + break; + } + break; + } + + _bounds *= Geom::Translate(Geom::IntPoint(dx, dy)); + + // Position must also be integer. + Geom::Point position = _position * _affine; + Geom::IntPoint iposition = position.floor(); + + _bounds *= Geom::Translate(iposition); + + // Queue redraw of new area + request_redraw(); + + _need_update = false; +} + + +static inline guint32 compose_xor(guint32 bg, guint32 fg, guint32 a) +{ + guint32 c = bg * (255-a) + (((bg ^ ~fg) + (bg >> 2) - (bg > 127 ? 63 : 0)) & 255) * a; + return (c + 127) / 255; +} + +/** + * Render ctrl to screen via Cairo. + */ +void CanvasItemCtrl::render(Inkscape::CanvasItemBuffer *buf) +{ + if (!buf) { + std::cerr << "CanvasItemCtrl::Render: No buffer!" << std::endl; + return; + } + + if (!_bounds.intersects(buf->rect)) { + return; // Control not inside buffer rectangle. + } + + if (!_visible) { + return; // Hidden. + } + + if (!_built) { + build_cache(buf->device_scale); + } + + Geom::Point c = _bounds.min() - buf->rect.min(); + int x = c.x(); // Must be pixel aligned. + int y = c.y(); + + buf->cr->save(); + + // This code works regardless of source type. + + // 1. Copy the affected part of output to a temporary surface + + // Size in device pixels. Does not set device scale. + int width = _width * buf->device_scale; + int height = _height * buf->device_scale; + auto work = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width, height); + cairo_surface_set_device_scale(work->cobj(), buf->device_scale, buf->device_scale); // No C++ API! + + auto cr = Cairo::Context::create(work); + cr->translate(-_bounds.left(), -_bounds.top()); + cr->set_source(buf->cr->get_target(), buf->rect.left(), buf->rect.top()); + cr->paint(); + // static int a = 0; + // std::string name0 = "ctrl0_" + _name + "_" + std::to_string(a++) + ".png"; + // work->write_to_png(name0); + + // 2. Composite the control on a temporary surface + work->flush(); + int strideb = work->get_stride(); + unsigned char *pxb = work->get_data(); + + // this code allow background become isolated from rendering so we can do things like outline overlay + cairo_pattern_t *pattern = _canvas->get_background_pattern()->cobj(); + guint32 backcolor = ink_cairo_pattern_get_argb32(pattern); + guint32 *p = _cache; + for (int i = 0; i < height; ++i) { + guint32 *pb = reinterpret_cast<guint32*>(pxb + i*strideb); + for (int j = 0; j < width; ++j) { + guint32 base = *pb; + guint32 cc = *p++; + guint32 ac = cc & 0xff; + if (*pb == 0 && cc != 0) { + base = backcolor; + } + if (ac == 0 && cc != 0) { + *pb++ = argb32_from_rgba(cc | 0x000000ff); + } else if (ac == 0) { + *pb++ = base; + } else if ( + _mode == CANVAS_ITEM_CTRL_MODE_XOR || + _mode == CANVAS_ITEM_CTRL_MODE_GRAYSCALED_XOR || + _mode == CANVAS_ITEM_CTRL_MODE_DESATURATED_XOR) + { + EXTRACT_ARGB32(base, ab,rb,gb,bb) + // here we get canvas color and if color to draw + // has opacity, we override base colors + // flattenig canvas color + EXTRACT_ARGB32(backcolor, abb,rbb,gbb,bbb) + if (abb != ab) { + rb = (ab/255.0) * rb + (1-(ab/255.0)) * rbb; + gb = (ab/255.0) * gb + (1-(ab/255.0)) * gbb; + bb = (ab/255.0) * bb + (1-(ab/255.0)) * bbb; + ab = 255; + } + guint32 ro = compose_xor(rb, (cc & 0xff000000) >> 24, ac); + guint32 go = compose_xor(gb, (cc & 0x00ff0000) >> 16, ac); + guint32 bo = compose_xor(bb, (cc & 0x0000ff00) >> 8, ac); + if (_mode == CANVAS_ITEM_CTRL_MODE_GRAYSCALED_XOR || + _mode == CANVAS_ITEM_CTRL_MODE_DESATURATED_XOR) { + guint32 gray = ro * 0.299 + go * 0.587 + bo * 0.114; + if (_mode == CANVAS_ITEM_CTRL_MODE_DESATURATED_XOR) { + double f = 0.85; // desaturate by 15% + double p = sqrt(ro * ro * 0.299 + go * go * 0.587 + bo * bo * 0.114); + ro = p + (ro - p) * f; + go = p + (go - p) * f; + bo = p + (bo - p) * f; + } else { + ro = gray; + go = gray; + bo = gray; + } + } + ASSEMBLE_ARGB32(px, ab,ro,go,bo) + *pb++ = px; + } else { + *pb++ = argb32_from_rgba(cc | 0x000000ff); + } + } + } + work->mark_dirty(); + // std::string name1 = "ctrl1_" + _name + "_" + std::to_string(a) + ".png"; + // work->write_to_png(name1); + + // 3. Replace the affected part of output with contents of temporary surface + buf->cr->set_source(work, x, y); + + + + buf->cr->rectangle(x, y, _width, _height); + buf->cr->clip(); + buf->cr->set_operator(Cairo::OPERATOR_SOURCE); + buf->cr->paint(); + buf->cr->restore(); +} + +void CanvasItemCtrl::set_fill(guint32 rgba) +{ + if (_fill != rgba) { + _fill = rgba; + _built = false; + request_redraw(); + } +} + +void CanvasItemCtrl::set_stroke(guint32 rgba) +{ + if (_stroke != rgba) { + _stroke = rgba; + _built = false; + request_redraw(); + } +} + +void CanvasItemCtrl::set_shape(int shape) +{ + if (_shape != shape) { + _shape = Inkscape::CanvasItemCtrlShape(shape); // Fixme + _built = false; + request_update(); // Geometry could change + } +} + +void CanvasItemCtrl::set_shape_default() +{ + switch (_type) { + case CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE: + _shape = CANVAS_ITEM_CTRL_SHAPE_DARROW; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW: + _shape = CANVAS_ITEM_CTRL_SHAPE_SARROW; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE: + _shape = CANVAS_ITEM_CTRL_SHAPE_CARROW; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER: + _shape = CANVAS_ITEM_CTRL_SHAPE_PIVOT; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN: + _shape = CANVAS_ITEM_CTRL_SHAPE_SALIGN; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN: + _shape = CANVAS_ITEM_CTRL_SHAPE_CALIGN; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN: + _shape = CANVAS_ITEM_CTRL_SHAPE_MALIGN; + break; + + case CANVAS_ITEM_CTRL_TYPE_NODE_AUTO: + case CANVAS_ITEM_CTRL_TYPE_ROTATE: + _shape = CANVAS_ITEM_CTRL_SHAPE_CIRCLE; + break; + + case CANVAS_ITEM_CTRL_TYPE_CENTER: + _shape = CANVAS_ITEM_CTRL_SHAPE_PLUS; + break; + + case CANVAS_ITEM_CTRL_TYPE_SHAPER: + case CANVAS_ITEM_CTRL_TYPE_LPE: + case CANVAS_ITEM_CTRL_TYPE_NODE_CUSP: + _shape = CANVAS_ITEM_CTRL_SHAPE_DIAMOND; + break; + + case CANVAS_ITEM_CTRL_TYPE_POINT: + _shape = CANVAS_ITEM_CTRL_SHAPE_CROSS; + break; + + default: + _shape = CANVAS_ITEM_CTRL_SHAPE_SQUARE; + } +} + +void CanvasItemCtrl::set_mode(int mode) +{ + if (_mode != mode) { + _mode = Inkscape::CanvasItemCtrlMode(mode); // Fixme + _built = false; + request_update(); + } +} + +void CanvasItemCtrl::set_pixbuf(GdkPixbuf *pixbuf) +{ + if (_pixbuf != pixbuf) { + _pixbuf = pixbuf; + _width = gdk_pixbuf_get_width(pixbuf); + _height = gdk_pixbuf_get_height(pixbuf); + _built = false; + request_update(); + } +} + +// Nominally width == height == size except possibly for pixmaps. +void CanvasItemCtrl::set_size(int size) +{ + if (_pixbuf) { + // std::cerr << "CanvasItemCtrl::set_size: Attempting to set size on pixbuf control!" << std::endl; + return; + } + if (_width != size + _extra || _height != size + _extra) { + _width = size + _extra; + _height = size + _extra; + _built = false; + request_update(); // Geometry change + } +} + +void CanvasItemCtrl::set_size_via_index(int size_index) +{ + // Size must always be an odd number to center on pixel. + + if (size_index < 1 || size_index > 15) { + std::cerr << "CanvasItemCtrl::set_size_via_index: size_index out of range!" << std::endl; + size_index = 3; + } + + int size = 0; + switch (_type) { + case CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE: + case CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW: + size = size_index * 2 + 7; + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE: + case CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER: + size = size_index * 2 + 9; // 2 larger than HANDLE/SKEW + break; + + case CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN: + case CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN: + case CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN: + size = size_index * 4 + 5; // Needs to be larger to allow for rotating. + break; + + case CANVAS_ITEM_CTRL_TYPE_POINT: + case CANVAS_ITEM_CTRL_TYPE_ROTATE: + case CANVAS_ITEM_CTRL_TYPE_CENTER: + case CANVAS_ITEM_CTRL_TYPE_SIZER: + case CANVAS_ITEM_CTRL_TYPE_SHAPER: + case CANVAS_ITEM_CTRL_TYPE_LPE: + case CANVAS_ITEM_CTRL_TYPE_NODE_AUTO: + case CANVAS_ITEM_CTRL_TYPE_NODE_CUSP: + size = size_index * 2 + 5; + break; + + case CANVAS_ITEM_CTRL_TYPE_NODE_SMOOTH: + case CANVAS_ITEM_CTRL_TYPE_NODE_SYMETRICAL: + size = size_index * 2 + 3; + break; + + case CANVAS_ITEM_CTRL_TYPE_INVISIPOINT: + size = 1; + break; + + case CANVAS_ITEM_CTRL_TYPE_ANCHOR: // vanishing point for 3D box and anchor for pencil + size = size_index * 2 + 1; + break; + + case CANVAS_ITEM_CTRL_TYPE_DEFAULT: + size = size_index * 2 + 1; + break; + + default: + g_warning("set_size_via_index: missing case for handle type: %d", static_cast<int>(_type)); + size = size_index * 2 + 1; + break; + } + + set_size(size); +} + +void CanvasItemCtrl::set_size_default() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int size = prefs->getIntLimited("/options/grabsize/value", 3, 1, 15); + set_size_via_index(size); +} + +void CanvasItemCtrl::set_size_extra(int extra) +{ + if (_extra != extra && _pixbuf == nullptr) { // Don't enlarge pixbuf! + _width += (extra - _extra); + _height += (extra - _extra); + _extra = extra; + _built = false; + request_update(); // Geometry change + } +} + +void CanvasItemCtrl::set_type(Inkscape::CanvasItemCtrlType type) +{ + if (_type != type) { + _type = type; + + // Use _type to set default values: + set_shape_default(); + set_size_default(); + _built = false; + request_update(); // Possible Geometry change + } +} + +void CanvasItemCtrl::set_angle(double angle) +{ + if (_angle != angle) { + _angle = angle; + request_update(); // Geometry change + } +} + +void CanvasItemCtrl::set_anchor(SPAnchorType anchor) +{ + if (_anchor != anchor) { + _anchor = anchor; + request_update(); // Geometry change + } +} + +// ---------- Protected ---------- + +// Helper function for build_cache(): +bool point_inside_triangle(Geom::Point p1,Geom::Point p2,Geom::Point p3, Geom::Point point){ + using Geom::X; + using Geom::Y; + double denominator = (p1[X]*(p2[Y] - p3[Y]) + p1[Y]*(p3[X] - p2[X]) + p2[X]*p3[Y] - p2[Y]*p3[X]); + double t1 = (point[X]*(p3[Y] - p1[Y]) + point[Y]*(p1[X] - p3[X]) - p1[X]*p3[Y] + p1[Y]*p3[X]) / denominator; + double t2 = (point[X]*(p2[Y] - p1[Y]) + point[Y]*(p1[X] - p2[X]) - p1[X]*p2[Y] + p1[Y]*p2[X]) / -denominator; + double see = t1 + t2; + return 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1 && see <= 1; +} + + +void draw_darrow(Cairo::RefPtr<Cairo::Context>cr, double size) { + + // Find points, starting from tip of one arrowhead, working clockwise. + /* 1 4 + ╱│ │╲ + ╱ └────────┘ ╲ + 0╱ 2 3 ╲5 + ╲ 8 7 ╱ + ╲ ┌────────┐ ╱ + ╲│9 6│╱ + */ + + // Length of arrowhead (not including stroke). + double delta = (size-1)/4.0; // Use unscaled width. + + // Tip of arrow (0) + double tip_x = 0.5; // At edge, allow room for stroke. + double tip_y = size/2.0; // Center, assuming width == height. + + // Outer corner (1) + double out_x = tip_x + delta; + double out_y = tip_y - delta; + + // Inner corner (2) + double in_x = out_x; + double in_y = out_y + (delta/2.0); + + double x0 = tip_x; double y0 = tip_y; + double x1 = out_x; double y1 = out_y; + double x2 = in_x; double y2 = in_y; + double x3 = size - in_x; double y3 = in_y; + double x4 = size - out_x; double y4 = out_y; + double x5 = size - tip_x; double y5 = tip_y; + double x6 = size - out_x; double y6 = size - out_y; + double x7 = size - in_x; double y7 = size - in_y; + double x8 = in_x; double y8 = size - in_y; + double x9 = out_x; double y9 = size - out_y; + + // Draw arrow + cr->move_to(x0, y0); + cr->line_to(x1, y1); + cr->line_to(x2, y2); + cr->line_to(x3, y3); + cr->line_to(x4, y4); + cr->line_to(x5, y5); + cr->line_to(x6, y6); + cr->line_to(x7, y7); + cr->line_to(x8, y8); + cr->line_to(x9, y9); + cr->close_path(); +} + +void draw_carrow(Cairo::RefPtr<Cairo::Context>cr, double size) { + + // Length of arrowhead (not including stroke). + double delta = (size-3)/4.0; // Use unscaled width. + + // Tip of arrow + double tip_x = 1.5; // Edge, allow room for stroke when rotated. + double tip_y = delta + 1.5; + + // Outer corner (1) + double out_x = tip_x + delta; + double out_y = tip_y - delta; + + // Inner corner (2) + double in_x = out_x; + double in_y = out_y + (delta/2.0); + + double x0 = tip_x; double y0 = tip_y; + double x1 = out_x; double y1 = out_y; + double x2 = in_x; double y2 = in_y; + double x3 = size - in_y; //double y3 = size - in_x; + double x4 = size - out_y; double y4 = size - out_x; + double x5 = size - tip_y; double y5 = size - tip_x; + double x6 = x5 - delta; double y6 = y4; + double x7 = x5 - delta/2.0; double y7 = y4; + double x8 = x1; //double y8 = y0 + delta/2.0; + double x9 = x1; double y9 = y0 + delta; + + // Draw arrow + cr->move_to(x0, y0); + cr->line_to(x1, y1); + cr->line_to(x2, y2); + cr->arc(x1, y4, x3-x2, 3.0*M_PI/2.0, 0); + cr->line_to(x4, y4); + cr->line_to(x5, y5); + cr->line_to(x6, y6); + cr->line_to(x7, y7); + cr->arc_negative(x1, y4, x7-x8, 0, 3.0*M_PI/2.0); + cr->line_to(x9, y9); + cr->close_path(); +} + +void draw_pivot(Cairo::RefPtr<Cairo::Context>cr, double size) { + + double delta4 = (size-5)/4.0; // Keep away from edge or will clip when rotating. + double delta8 = delta4/2; + + // Line start + double center = size/2.0; + + cr->move_to (center - delta8, center - 2*delta4 - delta8); + cr->rel_line_to ( delta4, 0 ); + cr->rel_line_to ( 0, delta4); + + cr->rel_line_to ( delta4, delta4); + + cr->rel_line_to ( delta4, 0 ); + cr->rel_line_to ( 0, delta4); + cr->rel_line_to (-delta4, 0 ); + + cr->rel_line_to (-delta4, delta4); + + cr->rel_line_to ( 0, delta4); + cr->rel_line_to (-delta4, 0 ); + cr->rel_line_to ( 0, -delta4); + + cr->rel_line_to (-delta4, -delta4); + + cr->rel_line_to (-delta4, 0 ); + cr->rel_line_to ( 0, -delta4); + cr->rel_line_to ( delta4, 0 ); + + cr->rel_line_to ( delta4, -delta4); + cr->close_path(); + + cr->begin_new_sub_path(); + cr->arc_negative(center, center, delta4, 0, -2 * M_PI); +} + +void draw_salign(Cairo::RefPtr<Cairo::Context>cr, double size) { + + // Triangle pointing at line. + + // Basic units. + double delta4 = (size-1)/4.0; // Use unscaled width. + double delta8 = delta4/2; + if (delta8 < 2) { + // Keep a minimum gap of at least one pixel (after stroking). + delta8 = 2; + } + + // Tip of triangle + double tip_x = size/2.0; // Center (also rotation point). + double tip_y = size/2.0; + + // Corner triangle position. + double outer = size/2.0 - delta4; + + // Outer line position + double oline = size/2.0 + (int)delta4; + + // Inner line position + double iline = size/2.0 + (int)delta8; + + // Draw triangle + cr->move_to(tip_x, tip_y); + cr->line_to(outer, outer); + cr->line_to(size - outer, outer); + cr->close_path(); + + // Draw line + cr->move_to(outer, iline); + cr->line_to(size - outer, iline); + cr->line_to(size - outer, oline); + cr->line_to(outer, oline); + cr->close_path(); +} + +void draw_calign(Cairo::RefPtr<Cairo::Context>cr, double size) { + + // Basic units. + double delta4 = (size-1)/4.0; // Use unscaled width. + double delta8 = delta4/2; + if (delta8 < 2) { + // Keep a minimum gap of at least one pixel (after stroking). + delta8 = 2; + } + + // Tip of triangle + double tip_x = size/2.0; // Center (also rotation point). + double tip_y = size/2.0; + + // Corner triangle position. + double outer = size/2.0 - delta8 - delta4; + + // End of line positin + double eline = size/2.0 - delta8; + + // Outer line position + double oline = size/2.0 + (int)delta4; + + // Inner line position + double iline = size/2.0 + (int)delta8; + + // Draw triangle + cr->move_to(tip_x, tip_y); + cr->line_to(outer, tip_y); + cr->line_to(tip_x, outer); + cr->close_path(); + + // Draw line + cr->move_to(iline, iline); + cr->line_to(iline, eline); + cr->line_to(oline, eline); + cr->line_to(oline, oline); + cr->line_to(eline, oline); + cr->line_to(eline, iline); + cr->close_path(); +} + +void draw_malign(Cairo::RefPtr<Cairo::Context>cr, double size) { + + // Basic units. + double delta4 = (size-1)/4.0; // Use unscaled width. + double delta8 = delta4/2; + if (delta8 < 2) { + // Keep a minimum gap of at least one pixel (after stroking). + delta8 = 2; + } + + // Tip of triangle + double tip_0 = size/2.0; + double tip_1 = size/2.0 - delta8; + + // Draw triangles + cr->move_to(tip_0, tip_1); + cr->line_to(tip_0 - delta4, tip_1 - delta4); + cr->line_to(tip_0 + delta4, tip_1 - delta4); + cr->close_path(); + + cr->move_to(size - tip_1, tip_0); + cr->line_to(size - tip_1 + delta4, tip_0 - delta4); + cr->line_to(size - tip_1 + delta4, tip_0 + delta4); + cr->close_path(); + + cr->move_to(size - tip_0, size - tip_1); + cr->line_to(size - tip_0 + delta4, size - tip_1 + delta4); + cr->line_to(size - tip_0 - delta4, size - tip_1 + delta4); + cr->close_path(); + + cr->move_to(tip_1, tip_0); + cr->line_to(tip_1 - delta4, tip_0 + delta4); + cr->line_to(tip_1 - delta4, tip_0 - delta4); + cr->close_path(); + +} + +void CanvasItemCtrl::build_cache(int device_scale) +{ + if (_width < 2 || _height < 2) { + return; // Nothing to render + } + + // Get colors + guint32 fill = 0x0; + guint32 stroke = 0x0; + fill = _fill; + stroke = _stroke; + + if (_shape != CANVAS_ITEM_CTRL_SHAPE_BITMAP) { + if (_width % 2 == 0 || _height % 2 == 0) { + std::cerr << "CanvasItemCtrl::build_cache: Width and/or height not odd integer! " + << _name << ": width: " << _width << " height: " << _height << std::endl; + } + } + + // Get memory for cache. + int width = _width * device_scale; // Not unsigned or math errors occur! + int height = _height * device_scale; + int size = width * height; + + if (_cache) delete[] _cache; + _cache = new guint32[size]; + guint32 *p = _cache; + + switch (_shape) { + + case CANVAS_ITEM_CTRL_SHAPE_SQUARE: + // Actually any rectanglular shape. + for (int i = 0; i < width; ++i) { + for (int j = 0; j < width; ++j) { + if (i + 1 > device_scale && device_scale < width - i && + j + 1 > device_scale && device_scale < height - j) { + *p++ = fill; + } else { + *p++ = stroke; + } + } + } + _built = true; + break; + + case CANVAS_ITEM_CTRL_SHAPE_DIAMOND: + { // Assume width == height. + int m = (width+1)/2; + + for (int i = 0; i < width; ++i) { + for (int j = 0; j < height; ++j) { + if ( i + j > m-1+device_scale && + (width-1-i) + j > m-1+device_scale && + (width-1-i) + (height-1-j) > m-1+device_scale && + i + (height-1-j) > m-1+device_scale ) { + *p++ = fill; + } else + if ( i + j > m-2 && + (width-1-i) + j > m-2 && + (width-1-i) + (height-1-j) > m-2 && + i + (height-1-j) > m-2 ) { + *p++ = stroke; + } else { + *p++ = 0; + } + } + } + _built = true; + break; + } + + case CANVAS_ITEM_CTRL_SHAPE_CIRCLE: + { // Assume width == height. + double rs = width/2.0; + double rs2 = rs*rs; + double rf = rs-device_scale; + double rf2 = rf*rf; + + for (int i = 0; i < width; ++i) { + for (int j = 0; j < height; ++j) { + + double rx = i - (width /2.0) + 0.5; + double ry = j - (height/2.0) + 0.5; + double r2 = rx*rx + ry*ry; + + if (r2 < rf2) { + *p++ = fill; + } else if (r2 < rs2) { + *p++ = stroke; + } else { + *p++ = 0; + } + } + } + _built = true; + break; + } + + case CANVAS_ITEM_CTRL_SHAPE_TRIANGLE: + { + Geom::Affine m = Geom::Translate(Geom::Point(-width/2.0,-height/2.0)); + m *= Geom::Rotate(-_angle); + m *= Geom::Translate(Geom::Point(width/2.0, height/2.0)); + + // Construct an arrowhead (triangle) of maximum size that won't leak out of rectangle + // defined by width and height, assuming width == height. + double w2 = width/2.0; + double h2 = height/2.0; + double w2cos = w2 * cos( M_PI/6 ); + double h2sin = h2 * sin( M_PI/6 ); + Geom::Point p1s(0, h2); + Geom::Point p2s(w2 + w2cos, h2 + h2sin); + Geom::Point p3s(w2 + w2cos, h2 - h2sin); + + // Needed for constructing smaller arrowhead below. + double theta = atan2( Geom::Point( p2s - p1s ) ); + + p1s *= m; + p2s *= m; + p3s *= m; + + // Construct a smaller arrow head for fill. + Geom::Point p1f(device_scale/sin(theta), h2); + Geom::Point p2f(w2 + w2cos, h2 - h2sin + device_scale/cos(theta)); + Geom::Point p3f(w2 + w2cos, h2 + h2sin - device_scale/cos(theta)); + p1f *= m; + p2f *= m; + p3f *= m; + + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + Geom::Point point = Geom::Point(x+0.5, y+0.5); + + if (point_inside_triangle(p1f, p2f, p3f, point)) { + p[(y*width)+x] = fill; + + } else if (point_inside_triangle(p1s, p2s, p3s, point)) { + p[(y*width)+x] = stroke; + + } else { + p[(y*width)+x] = 0; + } + } + } + break; + } + + case CANVAS_ITEM_CTRL_SHAPE_CROSS: + // Actually an 'X'. + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + if ( abs(x - y) < device_scale || + abs(width - 1 - x - y) < device_scale ) { + *p++ = stroke; + } else { + *p++ = 0; + } + } + } + _built = true; + break; + + case CANVAS_ITEM_CTRL_SHAPE_PLUS: + // Actually an '+'. + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + if ( std::abs(x-width/2) < device_scale || + std::abs(y-height/2) < device_scale ) { + *p++ = stroke; + } else { + *p++ = 0; + } + } + } + _built = true; + break; + + case CANVAS_ITEM_CTRL_SHAPE_DARROW: // Double arrow + case CANVAS_ITEM_CTRL_SHAPE_SARROW: // Same shape as darrow but rendered rotated 90 degrees. + case CANVAS_ITEM_CTRL_SHAPE_CARROW: // Double corner arrow + case CANVAS_ITEM_CTRL_SHAPE_PIVOT: // Fancy "plus" + case CANVAS_ITEM_CTRL_SHAPE_SALIGN: // Side align (triangle pointing toward line) + case CANVAS_ITEM_CTRL_SHAPE_CALIGN: // Corner align (triangle pointing into "L") + case CANVAS_ITEM_CTRL_SHAPE_MALIGN: // Middle align (four triangles poining inward) + { + double size = _width; // Use unscaled width. + + auto work = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, device_scale * size, device_scale * size); + cairo_surface_set_device_scale(work->cobj(), device_scale, device_scale); // No C++ API! + auto cr = Cairo::Context::create(work); + + // Rotate around center + cr->translate( size/2.0, size/2.0); + cr->rotate(_angle); + cr->translate(-size/2.0, -size/2.0); + + // Construct path + switch (_shape) { + case CANVAS_ITEM_CTRL_SHAPE_DARROW: + case CANVAS_ITEM_CTRL_SHAPE_SARROW: + draw_darrow(cr, size); + break; + + case CANVAS_ITEM_CTRL_SHAPE_CARROW: + draw_carrow(cr, size); + break; + + case CANVAS_ITEM_CTRL_SHAPE_PIVOT: + draw_pivot(cr, size); + break; + + case CANVAS_ITEM_CTRL_SHAPE_SALIGN: + draw_salign(cr, size); + break; + + case CANVAS_ITEM_CTRL_SHAPE_CALIGN: + draw_calign(cr, size); + break; + + case CANVAS_ITEM_CTRL_SHAPE_MALIGN: + draw_malign(cr, size); + break; + + default: + // Shouldn't happen + break; + } + + // Fill and stroke. + cr->set_source_rgba(SP_RGBA32_R_F(_fill), + SP_RGBA32_G_F(_fill), + SP_RGBA32_B_F(_fill), + SP_RGBA32_A_F(_fill)); + cr->fill_preserve(); + cr->set_source_rgba(SP_RGBA32_R_F(_stroke), + SP_RGBA32_G_F(_stroke), + SP_RGBA32_B_F(_stroke), + SP_RGBA32_A_F(_stroke)); + cr->set_line_width(1); + cr->stroke(); + + // Copy to buffer. + work->flush(); + int strideb = work->get_stride(); + unsigned char* pxb = work->get_data(); + guint32 *p = _cache; + for (int i = 0; i < device_scale * size; ++i) { + guint32 *pb = reinterpret_cast<guint32*>(pxb + i*strideb); + for (int j = 0; j < width; ++j) { + + guint32 color = 0x0; + + // Need to un-premultiply alpha and change order argb -> rgba. + guint32 alpha = (*pb & 0xff000000) >> 24; + if (alpha == 0x0) { + color = 0x0; + } else { + guint32 rgb = unpremul_alpha(*pb & 0xffffff, alpha); + color = (rgb << 8) + alpha; + } + + *p++ = color; + pb++; + } + } + _built = true; + break; + } + + case CANVAS_ITEM_CTRL_SHAPE_BITMAP: + { + if (_pixbuf) { + unsigned char* px = gdk_pixbuf_get_pixels (_pixbuf); + unsigned int rs = gdk_pixbuf_get_rowstride (_pixbuf); + for (int y = 0; y < height/device_scale; y++){ + for (int x = 0; x < width/device_scale; x++) { + unsigned char *s = px + rs*y + 4*x; + guint32 color; + if (s[3] < 0x80) { + color = 0; + } else if (s[0] < 0x80) { + color = _stroke; + } else { + color = _fill; + } + + // Fill in device_scale x device_scale block + for (int i = 0; i < device_scale; ++i) { + for (int j = 0; j < device_scale; ++j) { + guint* p = _cache + + (x * device_scale + i) + // Column + (y * device_scale + j) * width; // Row + *p = color; + } + } + } + } + } else { + std::cerr << "CanvasItemCtrl::build_cache: No bitmap!" << std::endl; + guint *p = _cache; + for (int y = 0; y < height/device_scale; y++){ + for (int x = 0; x < width/device_scale; x++) { + if (x == y) { + *p++ = 0xffff0000; + } else { + *p++ = 0; + } + } + } + } + _built = true; + break; + } + + case CANVAS_ITEM_CTRL_SHAPE_IMAGE: + std::cerr << "CanvasItemCtrl::build_cache: image: UNIMPLEMENTED" << std::endl; + break; + + default: + std::cerr << "CanvasItemCtrl::build_cache: unhandled shape!" << std::endl; + } +} + +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((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 : |