From a3dda4d25b4b6ed4d23e3003fa4538cb1c5df0e1 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Fri, 17 Mar 2017 10:01:27 +0100 Subject: Modifiy CtrlRect to work with rotated canvas. (bzr r15600) --- src/display/sodipodi-ctrlrect.cpp | 261 +++++++++++++++++--------------------- src/display/sodipodi-ctrlrect.h | 11 +- 2 files changed, 127 insertions(+), 145 deletions(-) diff --git a/src/display/sodipodi-ctrlrect.cpp b/src/display/sodipodi-ctrlrect.cpp index ecc952c48..b27a81ab3 100644 --- a/src/display/sodipodi-ctrlrect.cpp +++ b/src/display/sodipodi-ctrlrect.cpp @@ -6,9 +6,11 @@ * bulia byak * Carl Hetherington * Jon A. Cruz + * Tavmjong Bah * * Copyright (C) 1999-2001 Lauris Kaplinski * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2017 Tavmjong Bah * * Released under GNU GPL * @@ -18,6 +20,7 @@ #include "sp-canvas-util.h" #include "display/cairo-utils.h" #include "display/sp-canvas.h" +#include <2geom/transforms.h> /* * Currently we do not have point method, as it should always be painted @@ -76,14 +79,12 @@ void CtrlRect::init() _dashed = false; _checkerboard = false; - _shadow = 0; + _shadow_width = 0; _area = Geom::OptIntRect(); _rect = Geom::Rect(Geom::Point(0,0),Geom::Point(0,0)); - _shadow_size = 0; - _border_color = 0x000000ff; _fill_color = 0xffffffff; _shadow_color = 0x000000ff; @@ -98,52 +99,119 @@ void CtrlRect::render(SPCanvasBuf *buf) if (!_area) { return; } - Geom::IntRect area = *_area; - Geom::IntRect area_w_shadow (area[X].min(), area[Y].min(), - area[X].max() + _shadow_size, area[Y].max() + _shadow_size); - if ( area_w_shadow.intersects(buf->rect) ) + + Geom::IntRect area = *_area; // _area already includes space for shadow. + if ( area.intersects(buf->rect) ) { - static double const dashes[2] = {4.0, 4.0}; + cairo_save(buf->ct); cairo_translate(buf->ct, -buf->rect.left(), -buf->rect.top()); - cairo_set_line_width(buf->ct, 1); - if (_dashed) cairo_set_dash(buf->ct, dashes, 2, 0); - cairo_rectangle(buf->ct, 0.5 + area[X].min(), 0.5 + area[Y].min(), - area[X].max() - area[X].min(), area[Y].max() - area[Y].min()); + + // Get canvas rotation (scale is isotropic). + double rotation = atan2( _affine[1], _affine[0] ); + + // Are we axis aligned? + double mod_rot = fmod(rotation * M_2_PI, 1); + bool axis_aligned = Geom::are_near( mod_rot, 0 ) || Geom::are_near( mod_rot, 1.0 ); + + // Get the points we need transformed to window coordinates. + Geom::Point rect_transformed[4]; + for (unsigned i = 0; i < 4; ++i) { + rect_transformed[i] = _rect.corner(i) * _affine; + } + + // Draw shadow first. Shadow extends under rectangle to reduce aliasing effects. + if (_shadow_width > 0 && !_dashed) { + + // Offset by half stroke width (_shadow_width is in window coordinates). + Geom::Point shadow( _shadow_width/2.0, _shadow_width/2.0 ); + shadow *= Geom::Rotate( rotation ); + + if (axis_aligned) { + // Snap to pixel grid (add 0.5 to center on pixel). + cairo_move_to( buf->ct, + floor(rect_transformed[0][X]+shadow[X]+0.5) + 0.5, + floor(rect_transformed[0][Y]+shadow[Y]+0.5) + 0.5 ); + cairo_line_to( buf->ct, + floor(rect_transformed[1][X]+shadow[X]+0.5) + 0.5, + floor(rect_transformed[1][Y]+shadow[Y]+0.5) + 0.5 ); + cairo_line_to( buf->ct, + floor(rect_transformed[2][X]+shadow[X]+0.5) + 0.5, + floor(rect_transformed[2][Y]+shadow[Y]+0.5) + 0.5 ); + } else { + cairo_move_to( buf->ct, + rect_transformed[0][X]+shadow[X], + rect_transformed[0][Y]+shadow[Y] ); + cairo_line_to( buf->ct, + rect_transformed[1][X]+shadow[X], + rect_transformed[1][Y]+shadow[Y] ); + cairo_line_to( buf->ct, + rect_transformed[2][X]+shadow[X], + rect_transformed[2][Y]+shadow[Y] ); + } + + ink_cairo_set_source_rgba32( buf->ct, _shadow_color ); + cairo_set_line_width( buf->ct, _shadow_width + 1 ); + cairo_stroke( buf->ct ); + } + + // Setup rectangle path + if (axis_aligned) { + + // Snap to pixel grid + Geom::Rect outline( _rect.min() * _affine, _rect.max() * _affine); + // Check if there is a simpler way to go from 'outline' to 'int_rect'. + Geom::IntRect int_rect ( (int) floor(outline.min()[X] + 0.5), + (int) floor(outline.min()[Y] + 0.5), + (int) floor(outline.max()[X] + 0.5), + (int) floor(outline.max()[Y] + 0.5) ); + cairo_rectangle (buf->ct, + 0.5 + int_rect[X].min(), + 0.5 + int_rect[Y].min(), + int_rect[X].max() - int_rect[X].min(), + int_rect[Y].max() - int_rect[Y].min() ); + } else { + + // Angled + cairo_move_to( buf->ct, rect_transformed[0][X], rect_transformed[0][Y] ); + cairo_line_to( buf->ct, rect_transformed[1][X], rect_transformed[1][Y] ); + cairo_line_to( buf->ct, rect_transformed[2][X], rect_transformed[2][Y] ); + cairo_line_to( buf->ct, rect_transformed[3][X], rect_transformed[3][Y] ); + cairo_close_path( buf->ct ); + } + + // This doesn't seem to be used anywhere. If it is, then we should + // probably rotate the coordinate system and fill using a cairo_rectangle(). if (_checkerboard) { cairo_pattern_t *cb = ink_cairo_pattern_create_checkerboard(); cairo_set_source(buf->ct, cb); cairo_pattern_destroy(cb); cairo_fill_preserve(buf->ct); } + if (_has_fill) { ink_cairo_set_source_rgba32(buf->ct, _fill_color); cairo_fill_preserve(buf->ct); } + // Set up stroke. ink_cairo_set_source_rgba32(buf->ct, _border_color); - cairo_stroke(buf->ct); + cairo_set_line_width(buf->ct, 1); + static double const dashes[2] = {4.0, 4.0}; + if (_dashed) cairo_set_dash(buf->ct, dashes, 2, 0); - if (_shadow_size == 1) { // highlight the border by drawing it in _shadow_color - if (_dashed) { - cairo_set_dash(buf->ct, dashes, 2, 4); - cairo_rectangle(buf->ct, 0.5 + area[X].min(), 0.5 + area[Y].min(), - area[X].max() - area[X].min(), area[Y].max() - area[Y].min()); - } else { - cairo_rectangle(buf->ct, -0.5 + area[X].min(), -0.5 + area[Y].min(), - area[X].max() - area[X].min(), area[Y].max() - area[Y].min()); - } - ink_cairo_set_source_rgba32(buf->ct, _shadow_color); - cairo_stroke(buf->ct); - } else if (_shadow_size > 1) { // fill the shadow + // Stroke rectangle. + cairo_stroke_preserve(buf->ct); + + // Highlight the border by drawing it in _shadow_color. + if (_shadow_width == 1 && _dashed) { ink_cairo_set_source_rgba32(buf->ct, _shadow_color); - cairo_rectangle(buf->ct, 1 + area[X].max(), area[Y].min() + _shadow_size, - _shadow_size, area[Y].max() - area[Y].min() + 1); // right shadow - cairo_rectangle(buf->ct, area[X].min() + _shadow_size, 1 + area[Y].max(), - area[X].max() - area[X].min() - _shadow_size + 1, _shadow_size); - cairo_fill(buf->ct); + cairo_set_dash(buf->ct, dashes, 2, 4); // Dash offset by dash length. + cairo_stroke_preserve(buf->ct); } + + cairo_new_path( buf->ct ); // Clear path or get wierd artifacts. cairo_restore(buf->ct); } } @@ -158,124 +226,33 @@ void CtrlRect::update(Geom::Affine const &affine, unsigned int flags) (SP_CANVAS_ITEM_CLASS(sp_ctrlrect_parent_class))->update(this, affine, flags); } - sp_canvas_item_reset_bounds(this); + // Note: There is no harm if 'area' is too large other than a possible small slow down in + // rendering speed. - Geom::Rect bbox(_rect.min() * affine, _rect.max() * affine); + // Calculate an axis-aligned bounding box that include all of transformed _rect. + Geom::Rect bbox = _rect; + bbox *= affine; - Geom::OptIntRect _area_old = _area; - Geom::IntRect area ( (int) floor(bbox.min()[Geom::X] + 0.5), - (int) floor(bbox.min()[Geom::Y] + 0.5), - (int) floor(bbox.max()[Geom::X] + 0.5), - (int) floor(bbox.max()[Geom::Y] + 0.5) ); - _area = area; - Geom::IntRect area_old(0,0,0,0); - if (_area_old) { // this weird construction is because the code below assumes _area_old to be 'valid' - area_old = *_area_old; - } + // Enlarge bbox by twice shadow size (to allow for shadow on any side with a 45deg rotation). + bbox.expandBy( 2.0 *_shadow_width ); - gint _shadow_size_old = _shadow_size; - _shadow_size = _shadow; - - // FIXME: we don't process a possible change in _has_fill - if (_has_fill) { - if (_area_old) { - canvas->requestRedraw(area_old[X].min() - 1, area_old[Y].min() - 1, - area_old[X].max() + _shadow_size + 1, area_old[Y].max() + _shadow_size + 1); - } - if (_area) { - canvas->requestRedraw(area[X].min() - 1, area[Y].min() - 1, - area[X].max() + _shadow_size + 1, area[Y].max() + _shadow_size + 1); - } - } else { // clear box, be smart about what part of the frame to redraw - - /* Top */ - if (area[Y].min() != area_old[Y].min()) { // different level, redraw fully old and new - if (area_old[X].min() != area_old[X].max()) - canvas->requestRedraw(area_old[X].min() - 1, area_old[Y].min() - 1, - area_old[X].max() + 1, area_old[Y].min() + 1); - - if (area[X].min() != area[X].max()) - canvas->requestRedraw(area[X].min() - 1, area[Y].min() - 1, - area[X].max() + 1, area[Y].min() + 1); - } else { // same level, redraw only the ends - if (area[X].min() != area_old[X].min()) { - canvas->requestRedraw(MIN(area_old[X].min(),area[X].min()) - 1, area[Y].min() - 1, - MAX(area_old[X].min(),area[X].min()) + 1, area[Y].min() + 1); - } - if (area[X].max() != area_old[X].max()) { - canvas->requestRedraw(MIN(area_old[X].max(),area[X].max()) - 1, area[Y].min() - 1, - MAX(area_old[X].max(),area[X].max()) + 1, area[Y].min() + 1); - } - } - - /* Left */ - if (area[X].min() != area_old[X].min()) { // different level, redraw fully old and new - if (area_old[Y].min() != area_old[Y].max()) - canvas->requestRedraw(area_old[X].min() - 1, area_old[Y].min() - 1, - area_old[X].min() + 1, area_old[Y].max() + 1); - - if (area[Y].min() != area[Y].max()) - canvas->requestRedraw(area[X].min() - 1, area[Y].min() - 1, - area[X].min() + 1, area[Y].max() + 1); - } else { // same level, redraw only the ends - if (area[Y].min() != area_old[Y].min()) { - canvas->requestRedraw(area[X].min() - 1, MIN(area_old[Y].min(),area[Y].min()) - 1, - area[X].min() + 1, MAX(area_old[Y].min(),area[Y].min()) + 1); - } - if (area[Y].max() != area_old[Y].max()) { - canvas->requestRedraw(area[X].min() - 1, MIN(area_old[Y].max(),area[Y].max()) - 1, - area[X].min() + 1, MAX(area_old[Y].max(),area[Y].max()) + 1); - } - } - - /* Right */ - if (area[X].max() != area_old[X].max() || _shadow_size_old != _shadow_size) { - if (area_old[Y].min() != area_old[Y].max()) - canvas->requestRedraw(area_old[X].max() - 1, area_old[Y].min() - 1, - area_old[X].max() + _shadow_size + 1, area_old[Y].max() + _shadow_size + 1); - - if (area[Y].min() != area[Y].max()) - canvas->requestRedraw(area[X].max() - 1, area[Y].min() - 1, - area[X].max() + _shadow_size + 1, area[Y].max() + _shadow_size + 1); - } else { // same level, redraw only the ends - if (area[Y].min() != area_old[Y].min()) { - canvas->requestRedraw(area[X].max() - 1, MIN(area_old[Y].min(),area[Y].min()) - 1, - area[X].max() + _shadow_size + 1, MAX(area_old[Y].min(),area[Y].min()) + _shadow_size + 1); - } - if (area[Y].max() != area_old[Y].max()) { - canvas->requestRedraw(area[X].max() - 1, MIN(area_old[Y].max(),area[Y].max()) - 1, - area[X].max() + _shadow_size + 1, MAX(area_old[Y].max(),area[Y].max()) + _shadow_size + 1); - } - } + // Generate an integer rectangle that includes bbox. + Geom::OptIntRect _area_old = _area; + _area = bbox.roundOutwards(); - /* Bottom */ - if (area[Y].max() != area_old[Y].max() || _shadow_size_old != _shadow_size) { - if (area_old[X].min() != area_old[X].max()) - canvas->requestRedraw(area_old[X].min() - 1, area_old[Y].max() - 1, - area_old[X].max() + _shadow_size + 1, area_old[Y].max() + _shadow_size + 1); - - if (area[X].min() != area[X].max()) - canvas->requestRedraw(area[X].min() - 1, area[Y].max() - 1, - area[X].max() + _shadow_size + 1, area[Y].max() + _shadow_size + 1); - } else { // same level, redraw only the ends - if (area[X].min() != area_old[X].min()) { - canvas->requestRedraw(MIN(area_old[X].min(),area[X].min()) - 1, area[Y].max() - 1, - MAX(area_old[X].min(),area[X].min()) + _shadow_size + 1, area[Y].max() + _shadow_size + 1); - } - if (area[X].max() != area_old[X].max()) { - canvas->requestRedraw(MIN(area_old[X].max(),area[X].max()) - 1, area[Y].max() - 1, - MAX(area_old[X].max(),area[X].max()) + _shadow_size + 1, area[Y].max() + _shadow_size + 1); - } - } - } + // std::cout << " _rect: " << _rect << std::endl; + // std::cout << " bbox: " << bbox << std::endl; + // std::cout << " area: " << *_area << std::endl; - // update SPCanvasItem box if (_area) { - x1 = area[X].min() - 1; - y1 = area[Y].min() - 1; - x2 = area[X].max() + _shadow_size + 1; - y2 = area[Y].max() + _shadow_size + 1; + Geom::IntRect area = *_area; + sp_canvas_update_bbox( this, area[X].min(), area[Y].min(), area[X].max(), area[Y].max() ); + } else { + std::cerr << "CtrlRect::update: No area!!" << std::endl; } + + // At rendering stage, we need to know affine: + _affine = affine; } @@ -289,7 +266,7 @@ void CtrlRect::setColor(guint32 b, bool h, guint f) void CtrlRect::setShadow(int s, guint c) { - _shadow = s; + _shadow_width = s; _shadow_color = c; _requestUpdate(); } diff --git a/src/display/sodipodi-ctrlrect.h b/src/display/sodipodi-ctrlrect.h index 08a085649..02fc85647 100644 --- a/src/display/sodipodi-ctrlrect.h +++ b/src/display/sodipodi-ctrlrect.h @@ -4,14 +4,17 @@ /** * @file * Simple non-transformed rectangle, usable for rubberband. + * Modified to work with rotated canvas. */ /* * Authors: * Lauris Kaplinski * Carl Hetherington + * Tavmjong Bah * * Copyright (C) 1999-2001 Lauris Kaplinski * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2017 Tavmjong Bah * * Released under GNU GPL * @@ -21,6 +24,7 @@ #include "sp-canvas-item.h" #include <2geom/rect.h> #include <2geom/int-rect.h> +#include <2geom/transforms.h> struct SPCanvasBuf; @@ -48,23 +52,24 @@ private: void _requestUpdate(); Geom::Rect _rect; + Geom::Affine _affine; + bool _has_fill; bool _dashed; bool _checkerboard; Geom::OptIntRect _area; - gint _shadow_size; + gint _shadow_width; guint32 _border_color; guint32 _fill_color; guint32 _shadow_color; - int _shadow; }; struct CtrlRectClass : public SPCanvasItemClass {}; GType sp_ctrlrect_get_type(); -#endif // SEEN_RUBBERBAND_H +#endif // SEEN_CTRLRECT_H /* Local Variables: -- cgit v1.2.3