From eeb17625d1e3f7442edda72e729a22207ff2d7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 6 Dec 2016 09:41:43 +0100 Subject: [PATCH] [apps] Create a curve view to draw curves from any expression Change-Id: I6e1183a560042688c596e3aa2385ab3bf0dcda2c --- apps/Makefile | 1 + apps/curve_view.cpp | 176 ++++++++++++++++++++++++++++++++ apps/curve_view.h | 39 +++++++ apps/graph/graph/graph_view.cpp | 174 ++----------------------------- apps/graph/graph/graph_view.h | 64 ++++-------- 5 files changed, 247 insertions(+), 207 deletions(-) create mode 100644 apps/curve_view.cpp create mode 100644 apps/curve_view.h diff --git a/apps/Makefile b/apps/Makefile index e06078311..586c4bbe5 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -7,6 +7,7 @@ include apps/calculation/Makefile app_objs += $(addprefix apps/,\ apps_container.o\ constant.o\ + curve_view.o\ expression_text_field_delegate.o\ float_parameter_controller.o\ main.o\ diff --git a/apps/curve_view.cpp b/apps/curve_view.cpp new file mode 100644 index 000000000..406124c44 --- /dev/null +++ b/apps/curve_view.cpp @@ -0,0 +1,176 @@ +#include "curve_view.h" +#include "constant.h" +#include +#include +#include + +constexpr KDColor CurveView::k_axisColor; + +CurveView::CurveView() : + View() +{ +} + +void CurveView::reload() { + markRectAsDirty(bounds()); +} + +KDCoordinate CurveView::pixelLength(Axis axis) const { + assert(axis == Axis::Horizontal || axis == Axis::Vertical); + return (axis == Axis::Horizontal ? m_frame.width() : m_frame.height()); +} + +float CurveView::pixelToFloat(Axis axis, KDCoordinate p) const { + KDCoordinate pixels = axis == Axis::Horizontal ? p : pixelLength(axis)-p; + return min(axis) + pixels*(max(axis)-min(axis))/pixelLength(axis); +} + +float CurveView::floatToPixel(Axis axis, float f) const { + float fraction = (f-min(axis))/(max(axis)-min(axis)); + fraction = axis == Axis::Horizontal ? fraction : 1.0f - fraction; + return pixelLength(axis)*fraction; +} + +void CurveView::drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness) const { + KDRect lineRect = KDRectZero; + switch(axis) { + // WARNING TODO: anti-aliasing? + case Axis::Horizontal: + lineRect = KDRect( + rect.x(), floatToPixel(Axis::Vertical, coordinate), + rect.width(), thickness + ); + break; + case Axis::Vertical: + lineRect = KDRect( + floatToPixel(Axis::Horizontal, coordinate), rect.y(), + thickness, rect.height() + ); + break; + } + ctx->fillRect(lineRect, color); +} + +void CurveView::drawAxes(Axis axis, KDContext * ctx, KDRect rect) const { + drawLine(ctx, rect, axis, 0.0f, k_axisColor, 2); +} + +#define LINE_THICKNESS 3 + +#if LINE_THICKNESS == 3 + +constexpr KDCoordinate circleDiameter = 3; +constexpr KDCoordinate stampSize = circleDiameter+1; +const uint8_t stampMask[stampSize+1][stampSize+1] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0x7A, 0x0C, 0x7A, 0xFF}, + {0xFF, 0x0C, 0x00, 0x0C, 0xFF}, + {0xFF, 0x7A, 0x0C, 0x7A, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF} +}; + +#elif LINE_THICKNESS == 5 + +constexpr KDCoordinate circleDiameter = 5; +constexpr KDCoordinate stampSize = circleDiameter+1; +const uint8_t stampMask[stampSize+1][stampSize+1] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF}, + {0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF}, + {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0xFF}, + {0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF}, + {0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, +}; + +#endif + +constexpr static int k_maxNumberOfIterations = 10; + +void CurveView::drawExpression(Expression * expression, KDColor color, KDContext * ctx, KDRect rect) const { + float xMin = min(Axis::Horizontal); + float xMax = max(Axis::Horizontal); + float xStep = (xMax-xMin)/320.0f; + for (float x = xMin; x < xMax; x += xStep) { + float y = evaluateExpressionAtAbscissa(expression, x); + float pxf = floatToPixel(Axis::Horizontal, x); + float pyf = floatToPixel(Axis::Vertical, y); + stampAtLocation(pxf, pyf, color, ctx); + if (x > xMin) { + jointDots(expression, x - xStep, evaluateExpressionAtAbscissa(expression, x-xStep), x, y, color, k_maxNumberOfIterations, ctx); + } + } +} + +void CurveView::stampAtLocation(float pxf, float pyf, KDColor color, KDContext * ctx) const { + // We avoid drawing when no part of the stamp is visible + if (pyf < -stampSize || pyf > pixelLength(Axis::Vertical)+stampSize) { + return; + } + uint8_t shiftedMask[stampSize][stampSize]; + KDColor workingBuffer[stampSize*stampSize]; + KDCoordinate px = pxf; + KDCoordinate py = pyf; + float dx = pxf - floorf(pxf); + float dy = pyf - floorf(pyf); + /* TODO: this could be optimized by precomputing 10 or 100 shifted masks. The + * dx and dy would be rounded to one tenth or one hundredth to choose the + * right shifted mask. */ + for (int i=0; iblendRectWithMask(stampRect, color, (const uint8_t *)shiftedMask, workingBuffer); +} + +void CurveView::jointDots(Expression * expression, float x, float y, float u, float v, KDColor color, int maxNumberOfRecursion, KDContext * ctx) const { + float pyf = floatToPixel(Axis::Vertical, y); + float pvf = floatToPixel(Axis::Vertical, v); + // No need to draw if both dots are outside visible area + if ((pyf < -stampSize && pvf < -stampSize) || (pyf > pixelLength(Axis::Vertical)+stampSize && pvf > pixelLength(Axis::Vertical)+stampSize)) { + return; + } + // If one of the dot is infinite, we cap it with a dot outside area + if (isinf(pyf)) { + pyf = pyf > 0 ? pixelLength(Axis::Vertical)+stampSize : -stampSize; + } + if (isinf(pvf)) { + pvf = pvf > 0 ? pixelLength(Axis::Vertical)+stampSize : -stampSize; + } + if (pyf - (float)circleDiameter/2.0f < pvf && pvf < pyf + (float)circleDiameter/2.0f) { + // the dots are already joined + return; + } + // C is the dot whose abscissa is between x and u + float cx = (x + u)/2.0f; + float cy = evaluateExpressionAtAbscissa(expression, cx); + if ((y < cy && cy < v) || (v < cy && cy < y)) { + /* As the middle dot is vertically between the two dots, we assume that we + * can draw a 'straight' line between the two */ + float pxf = floatToPixel(Axis::Horizontal, x); + float puf = floatToPixel(Axis::Horizontal, u); + straightJoinDots(pxf, pyf, puf, pvf, color, ctx); + return; + } + float pcxf = floatToPixel(Axis::Horizontal, cx); + float pcyf = floatToPixel(Axis::Vertical, cy); + if (maxNumberOfRecursion > 0) { + stampAtLocation(pcxf, pcyf, color, ctx); + jointDots(expression, x, y, cx, cy, color, maxNumberOfRecursion-1, ctx); + jointDots(expression, cx, cy, u, v, color, maxNumberOfRecursion-1, ctx); + } +} + +void CurveView::straightJoinDots(float pxf, float pyf, float puf, float pvf, KDColor color, KDContext * ctx) const { + if (pyf < pvf) { + for (float pnf = pyf; pnf +#include + +class CurveView : public View { +public: + CurveView(); + void reload(); +protected: + enum class Axis { + Horizontal = 0, + Vertical = 1 + }; + constexpr static KDColor k_axisColor = KDColor::RGB24(0x000000); + virtual float min(Axis axis) const = 0; + virtual float max(Axis axis) const = 0; + KDCoordinate pixelLength(Axis axis) const; + float pixelToFloat(Axis axis, KDCoordinate p) const; + float floatToPixel(Axis axis, float f) const; + void drawLine(KDContext * ctx, KDRect rect, Axis axis, + float coordinate, KDColor color, KDCoordinate thickness = 1) const; + void drawAxes(Axis axis, KDContext * ctx, KDRect rect) const; + void drawExpression(Expression * expression, KDColor color, KDContext * ctx, KDRect rect) const; +private: + virtual float evaluateExpressionAtAbscissa(Expression * expression, float abscissa) const = 0; + /* Recursively join two dots (dichotomy). The method stops when the + * maxNumberOfRecursion in reached. */ + void jointDots(Expression * expression, float x, float y, float u, float v, KDColor color, int maxNumberOfRecursion, KDContext * ctx) const; + /* Join two dots with a straight line. */ + void straightJoinDots(float pxf, float pyf, float puf, float pvf, KDColor color, KDContext * ctx) const; + /* Stamp centered around (pxf, pyf). If pxf and pyf are not round number, the + * function shifts the stamp (by blending adjacent pixel colors) to draw with + * anti alising. */ + void stampAtLocation(float pxf, float pyf, KDColor color, KDContext * ctx) const; +}; + +#endif diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index cca068b4a..2ebbbdccb 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -6,11 +6,10 @@ namespace Graph { -constexpr KDColor GraphView::k_axisColor; constexpr KDColor GraphView::k_gridColor; GraphView::GraphView(FunctionStore * functionStore, AxisInterval * axisInterval) : - View(), + CurveView(), m_cursorView(CursorView()), m_xCursorPosition(-1.0f), m_yCursorPosition(-1.0f), @@ -233,33 +232,12 @@ void GraphView::layoutSubviews() { void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, KDColorWhite); drawGrid(ctx, rect); - drawAxes(ctx, rect); - drawFunction(ctx, rect); -} - -void GraphView::drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness) const { - KDRect lineRect = KDRectZero; - switch(axis) { - // WARNING TODO: anti-aliasing? - case Axis::Horizontal: - lineRect = KDRect( - rect.x(), floatToPixel(Axis::Vertical, coordinate), - rect.width(), thickness - ); - break; - case Axis::Vertical: - lineRect = KDRect( - floatToPixel(Axis::Horizontal, coordinate), rect.y(), - thickness, rect.height() - ); - break; + drawAxes(Axis::Horizontal, ctx, rect); + drawAxes(Axis::Vertical, ctx, rect); + for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) { + Function * f = m_functionStore->activeFunctionAtIndex(i); + drawExpression(f->expression(), f->color(), ctx, rect); } - ctx->fillRect(lineRect, color); -} - -void GraphView::drawAxes(KDContext * ctx, KDRect rect) const { - drawLine(ctx, rect, Axis::Horizontal, 0.0f, k_axisColor, 2); - drawLine(ctx, rect, Axis::Vertical, 0.0f, k_axisColor, 2); } void GraphView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor color) const { @@ -285,143 +263,9 @@ float GraphView::max(Axis axis) const { return (axis == Axis::Horizontal ? m_axisInterval->xMax() : m_axisInterval->yMax()); } -KDCoordinate GraphView::pixelLength(Axis axis) const { - assert(axis == Axis::Horizontal || axis == Axis::Vertical); - return (axis == Axis::Horizontal ? m_frame.width() : m_frame.height()); -} - -float GraphView::pixelToFloat(Axis axis, KDCoordinate p) const { - KDCoordinate pixels = axis == Axis::Horizontal ? p : pixelLength(axis)-p; - return min(axis) + pixels*(max(axis)-min(axis))/pixelLength(axis); -} - -float GraphView::floatToPixel(Axis axis, float f) const { - float fraction = (f-min(axis))/(max(axis)-min(axis)); - fraction = axis == Axis::Horizontal ? fraction : 1.0f - fraction; - return pixelLength(axis)*fraction; -} - -#define LINE_THICKNESS 3 - -#if LINE_THICKNESS == 3 - -constexpr KDCoordinate circleDiameter = 3; -constexpr KDCoordinate stampSize = circleDiameter+1; -const uint8_t stampMask[stampSize+1][stampSize+1] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0x7A, 0x0C, 0x7A, 0xFF}, - {0xFF, 0x0C, 0x00, 0x0C, 0xFF}, - {0xFF, 0x7A, 0x0C, 0x7A, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF} -}; - -#elif LINE_THICKNESS == 5 - -constexpr KDCoordinate circleDiameter = 5; -constexpr KDCoordinate stampSize = circleDiameter+1; -const uint8_t stampMask[stampSize+1][stampSize+1] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF}, - {0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF}, - {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0xFF}, - {0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF}, - {0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, -}; - -#endif - -constexpr static int k_maxNumberOfIterations = 10; - -void GraphView::drawFunction(KDContext * ctx, KDRect rect) const { - float xMin = m_axisInterval->xMin(); - float xMax = m_axisInterval->xMax(); - float xStep = (xMax-xMin)/320.0f; - for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) { - Function * f = m_functionStore->activeFunctionAtIndex(i); - for (float x = xMin; x < xMax; x += xStep) { - float y = f->evaluateAtAbscissa(x, m_evaluateContext); - float pxf = floatToPixel(Axis::Horizontal, x); - float pyf = floatToPixel(Axis::Vertical, y); - stampAtLocation(pxf, pyf, f->color(), ctx); - if (x > xMin) { - jointDots(x - xStep, f->evaluateAtAbscissa(x-xStep, m_evaluateContext), x, y, f, k_maxNumberOfIterations, ctx); - } - } - } -} - -void GraphView::stampAtLocation(float pxf, float pyf, KDColor color, KDContext * ctx) const { - // We avoid drawing when no part of the stamp is visible - if (pyf < -stampSize || pyf > pixelLength(Axis::Vertical)+stampSize) { - return; - } - uint8_t shiftedMask[stampSize][stampSize]; - KDColor workingBuffer[stampSize*stampSize]; - KDCoordinate px = pxf; - KDCoordinate py = pyf; - float dx = pxf - floorf(pxf); - float dy = pyf - floorf(pyf); - /* TODO: this could be optimized by precomputing 10 or 100 shifted masks. The - * dx and dy would be rounded to one tenth or one hundredth to choose the - * right shifted mask. */ - for (int i=0; iblendRectWithMask(stampRect, color, (const uint8_t *)shiftedMask, workingBuffer); -} - -void GraphView::jointDots(float x, float y, float u, float v, Function * function, int maxNumberOfRecursion, KDContext * ctx) const { - float pyf = floatToPixel(Axis::Vertical, y); - float pvf = floatToPixel(Axis::Vertical, v); - // No need to draw if both dots are outside visible area - if ((pyf < -stampSize && pvf < -stampSize) || (pyf > pixelLength(Axis::Vertical)+stampSize && pvf > pixelLength(Axis::Vertical)+stampSize)) { - return; - } - // If one of the dot is infinite, we cap it with a dot outside area - if (isinf(pyf)) { - pyf = pyf > 0 ? pixelLength(Axis::Vertical)+stampSize : -stampSize; - } - if (isinf(pvf)) { - pvf = pvf > 0 ? pixelLength(Axis::Vertical)+stampSize : -stampSize; - } - if (pyf - (float)circleDiameter/2.0f < pvf && pvf < pyf + (float)circleDiameter/2.0f) { - // the dots are already joined - return; - } - // C is the dot whose abscissa is between x and u - float cx = (x + u)/2.0f; - float cy = function->evaluateAtAbscissa(cx, m_evaluateContext); - if ((y < cy && cy < v) || (v < cy && cy < y)) { - /* As the middle dot is vertically between the two dots, we assume that we - * can draw a 'straight' line between the two */ - float pxf = floatToPixel(Axis::Horizontal, x); - float puf = floatToPixel(Axis::Horizontal, u); - straightJoinDots(pxf, pyf, puf, pvf, function->color(), ctx); - return; - } - float pcxf = floatToPixel(Axis::Horizontal, cx); - float pcyf = floatToPixel(Axis::Vertical, cy); - if (maxNumberOfRecursion > 0) { - stampAtLocation(pcxf, pcyf, function->color(), ctx); - jointDots(x, y, cx, cy, function, maxNumberOfRecursion-1, ctx); - jointDots(cx, cy, u, v, function, maxNumberOfRecursion-1, ctx); - } -} - -void GraphView::straightJoinDots(float pxf, float pyf, float puf, float pvf, KDColor color, KDContext * ctx) const { - if (pyf < pvf) { - for (float pnf = pyf; pnfsetOverridenValueForSymbolX(abscissa); + return expression->approximate(*m_evaluateContext); } } diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index 900e27821..18239af55 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -4,12 +4,13 @@ #include #include "cursor_view.h" #include "axis_interval.h" +#include "../../curve_view.h" #include "../function_store.h" #include "../evaluate_context.h" namespace Graph { -class GraphView : public View { +class GraphView : public CurveView { public: GraphView(FunctionStore * functionStore, AxisInterval * axisInterval); void drawRect(KDContext * ctx, KDRect rect) const override; @@ -27,47 +28,7 @@ public: int indexFunctionSelectedByCursor(); void reload(); private: - constexpr static KDColor k_axisColor = KDColor::RGB24(0x000000); constexpr static KDColor k_gridColor = KDColor::RGB24(0xEEEEEE); - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; - - enum class Axis { - Horizontal = 0, - Vertical = 1 - }; - - float min(Axis axis) const; - float max(Axis axis) const; - KDCoordinate pixelLength(Axis axis) const; - - float pixelToFloat(Axis axis, KDCoordinate p) const; - float floatToPixel(Axis axis, float f) const; - - void drawLine(KDContext * ctx, KDRect rect, Axis axis, - float coordinate, KDColor color, KDCoordinate thickness = 1) const; - - void drawAxes(KDContext * ctx, KDRect rect) const; - void drawGrid(KDContext * ctx, KDRect rect) const; - void drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor color) const; - void drawFunction(KDContext * ctx, KDRect rect) const; - /* Recursively join two dots (dichotomy). The method stops when the - * maxNumberOfRecursion in reached. */ - void jointDots(float x, float y, float u, float v, Function * function, int maxNumberOfRecursion, KDContext * ctx) const; - /* Join two dots with a straight line. */ - void straightJoinDots(float pxf, float pyf, float puf, float pvf, KDColor color, KDContext * ctx) const; - /* Stamp centered around (pxf, pyf). If pxf and pyf are not round number, the - * function shifts the stamp (by blending adjacent pixel colors) to draw with - * anti alising. */ - void stampAtLocation(float pxf, float pyf, KDColor color, KDContext * ctx) const; - int numberOfXLabels() const; - int numberOfYLabels() const; - CursorView m_cursorView; - float m_xCursorPosition; - float m_yCursorPosition; - int m_indexFunctionSelectedByCursor; - bool m_visibleCursor; constexpr static KDCoordinate k_cursorSize = 10; constexpr static float k_cursorMarginToBorder = 5.0f; constexpr static KDCoordinate k_labelWidth = 32; @@ -75,11 +36,30 @@ private: constexpr static KDCoordinate k_labelMargin = 4; constexpr static int k_maxNumberOfXLabels = 18; constexpr static int k_maxNumberOfYLabels = 13; + + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + + float min(Axis axis) const override; + float max(Axis axis) const override; + float evaluateExpressionAtAbscissa(Expression * expression, float abscissa) const override; + void drawGrid(KDContext * ctx, KDRect rect) const; + void drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor color) const; + + int numberOfXLabels() const; + int numberOfYLabels() const; + + CursorView m_cursorView; + float m_xCursorPosition; + float m_yCursorPosition; + int m_indexFunctionSelectedByCursor; + bool m_visibleCursor; + BufferTextView m_xLabels[k_maxNumberOfXLabels]; BufferTextView m_yLabels[k_maxNumberOfYLabels]; AxisInterval * m_axisInterval; - FunctionStore * m_functionStore; EvaluateContext * m_evaluateContext; };