From 420dd0476635ba1de02522e0a282f0f2e63decbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 3 Jun 2020 12:17:08 +0200 Subject: [PATCH] [apps/shared][python/port] CurveView::drawArrow uses pixel computation instead of float computation to avoid precision errors, by default the arrow size is decided in pixels. --- apps/shared/curve_view.cpp | 64 +++++++++++++++---- apps/shared/curve_view.h | 13 ++-- .../port/mod/matplotlib/pyplot/modpyplot.cpp | 4 +- .../port/mod/matplotlib/pyplot/plot_store.h | 2 +- .../port/mod/matplotlib/pyplot/plot_view.cpp | 2 +- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 13c33c7d2..9feec4648 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -133,6 +133,22 @@ float CurveView::floatToPixel(Axis axis, float f) const { } } +float CurveView::floatLengthToPixelLength(Axis axis, float f) const { + float dist = floatToPixel(axis, f) - floatToPixel(axis, 0.0f); + return axis == Axis::Vertical ? - dist : dist; +} + +float CurveView::floatLengthToPixelLength(float dx, float dy) const { + float dxPixel = floatLengthToPixelLength(Axis::Horizontal, dx); + float dyPixel = floatLengthToPixelLength(Axis::Vertical, dy); + return std::sqrt(dxPixel*dxPixel+dyPixel*dyPixel); +} + +float CurveView::pixelLengthToFloatLength(Axis axis, float f) const { + f = axis == Axis::Vertical ? -f : f; + return pixelToFloat(axis, floatToPixel(axis, 0.0f) + f); +} + void CurveView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor boldColor, KDColor lightColor) const { Axis otherAxis = (axis == Axis::Horizontal) ? Axis::Vertical : Axis::Horizontal; /* We translate the pixel coordinates into floats, adding/subtracting 1 to @@ -449,7 +465,27 @@ void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor } -void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWidth, float tanAngle) const { +void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWith, float tanAngle) const { + /* TODO: all computations are done in pixels because doing them in float + * values led to approximation error (?). But this leads to useless back and + * forth between float and pixels. Find a proper way to handle approximation + * errors (if this was the problem). */ + + assert(tanAngle >= 0.0f); + if (std::fabs(dx) < FLT_EPSILON && std::fabs(dy) < FLT_EPSILON) { + // We can't draw an arrow without any orientation + return; + } + + // Turn arrowWith in pixel length + float pixelArrowWith = 8.0f; // default value in pixels + if (arrowWith > 0.0f) { + float dxdy = std::sqrt(dx*dx+dy*dy); + float dxArrow = arrowWith*dx/dxdy; + float dyArrow = arrowWith*dy/dxdy; + pixelArrowWith = floatLengthToPixelLength(dxArrow, dyArrow); + } + /* Let's call the following variables L and l: * * / | @@ -466,22 +502,22 @@ void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float * * ----- L ----- * - **/ - assert(tanAngle >= 0.0f); - if (std::fabs(dx) < FLT_EPSILON && std::fabs(dy) < FLT_EPSILON) { - // We can't draw an arrow without any orientation - return; - } - float l = arrowWidth/2.0f; - float L = l/tanAngle; - float dx2dy2 = std::sqrt(dx*dx+dy*dy); + */ - float arrow1dx = L*dx/dx2dy2 + l*dy/dx2dy2; - float arrow1dy = L*dy/dx2dy2 - l*dx/dx2dy2; + float l = pixelArrowWith/2.0; + float L = l/tanAngle; + + // We compute the arrow segments in pixels + float dxPixel = floatLengthToPixelLength(Axis::Horizontal, dx); + float dyPixel = floatLengthToPixelLength(Axis::Vertical, dy); + float dx2dy2Pixel = floatLengthToPixelLength(dx, dy); + + float arrow1dx = pixelLengthToFloatLength(Axis::Horizontal, L*dxPixel/dx2dy2Pixel + l*dyPixel/dx2dy2Pixel); + float arrow1dy = pixelLengthToFloatLength(Axis::Vertical, L*dyPixel/dx2dy2Pixel - l*dxPixel/dx2dy2Pixel); drawSegment(ctx, rect, x, y, x - arrow1dx, y - arrow1dy, color, false); - float arrow2dx = L*dx/dx2dy2 - l*dy/dx2dy2; - float arrow2dy = L*dy/dx2dy2 + l*dx/dx2dy2; + float arrow2dx = pixelLengthToFloatLength(Axis::Horizontal, L*dxPixel/dx2dy2Pixel - l*dyPixel/dx2dy2Pixel); + float arrow2dy = pixelLengthToFloatLength(Axis::Vertical, L*dyPixel/dx2dy2Pixel + l*dxPixel/dx2dy2Pixel); drawSegment(ctx, rect, x, y, x - arrow2dx, y - arrow2dy, color, false); } diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 4f8da4eff..d87af23d6 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -57,6 +57,9 @@ protected: constexpr static int k_externRectMargin = 2; float pixelToFloat(Axis axis, KDCoordinate p) const; float floatToPixel(Axis axis, float f) const; + float floatLengthToPixelLength(Axis axis, float f) const; + float pixelLengthToFloatLength(Axis axis, float f) const; + float floatLengthToPixelLength(float dx, float dy) const; void drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const { return drawHorizontalOrVerticalSegment(ctx, rect, axis, coordinate, -INFINITY, INFINITY, color, @@ -77,9 +80,9 @@ protected: void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Small) const; /* 'drawArrow' draws the edge of an arrow pointing to (x,y) with the * orientation (dx,dy). - * The parameters defining the shape of the arrow are the length of the base - * of the arrow triangle - 'arrowWith' - and the tangent of the angle between - * the segment and each wing of the arrow called 'tanAngle'. + * The parameters defining the shape of the arrow are the length of + * the base of the arrow triangle - 'pixelArrowWith' - and the tangent of the + * angle between the segment and each wing of the arrow called 'tanAngle'. * * / | * / | @@ -95,11 +98,11 @@ protected: * * <--- L ---> * - * l = arrowWith + * l = pixelArrowWith * tanAngle = tan(angle) = l/2L */ - void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWith = 4, float tanAngle = 1.0f/3.0f) const; + void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWith, float tanAngle = 1.0f/3.0f) const; void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; diff --git a/python/port/mod/matplotlib/pyplot/modpyplot.cpp b/python/port/mod/matplotlib/pyplot/modpyplot.cpp index 8551af593..c81eeb7bf 100644 --- a/python/port/mod/matplotlib/pyplot/modpyplot.cpp +++ b/python/port/mod/matplotlib/pyplot/modpyplot.cpp @@ -106,7 +106,9 @@ mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args) mp_map_elem_t * elem; // Setting arrow width elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_head_width), MP_MAP_LOOKUP); - mp_obj_t arrowWidth = (elem == nullptr) ? mp_obj_new_float(0.003) : elem->value; + /* Default head_width is 0.0f because we want a default width in pixel + * coordinates which is handled by CurveView::drawArrow. */ + mp_obj_t arrowWidth = (elem == nullptr) ? mp_obj_new_float(0.0f) : elem->value; // Setting arrow color KDColor color; diff --git a/python/port/mod/matplotlib/pyplot/plot_store.h b/python/port/mod/matplotlib/pyplot/plot_store.h index 2dfda039e..6009bbf76 100644 --- a/python/port/mod/matplotlib/pyplot/plot_store.h +++ b/python/port/mod/matplotlib/pyplot/plot_store.h @@ -78,7 +78,7 @@ public: KDColor m_color; }; - void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, mp_obj_t arrowWidth = mp_obj_new_float(0.0)); + void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, mp_obj_t arrowWidth = mp_obj_new_float(NAN)); Iterable> segments() { return Iterable>(m_segments); } // Rect diff --git a/python/port/mod/matplotlib/pyplot/plot_view.cpp b/python/port/mod/matplotlib/pyplot/plot_view.cpp index 48100bec6..274e3b132 100644 --- a/python/port/mod/matplotlib/pyplot/plot_view.cpp +++ b/python/port/mod/matplotlib/pyplot/plot_view.cpp @@ -46,7 +46,7 @@ void PlotView::traceSegment(KDContext * ctx, KDRect r, PlotStore::Segment segmen segment.xEnd(), segment.yEnd(), segment.color() ); - if (segment.arrowWidth() > 0.0f) { + if (!std::isnan(segment.arrowWidth())) { float dx = segment.xEnd() - segment.xStart(); float dy = segment.yEnd() - segment.yStart(); drawArrow(ctx, r, segment.xEnd(), segment.yEnd(), dx, dy, segment.color(), segment.arrowWidth());