diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index e8a39cde8..791679f05 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -1,5 +1,6 @@ #include "graph_view.h" #include +#include namespace Graph { @@ -8,8 +9,6 @@ constexpr KDColor kMainGridColor = KDColor::RGB24(0xCCCCCC); constexpr KDColor kSecondaryGridColor = KDColor::RGB24(0xEEEEEE); constexpr int kNumberOfMainGridLines = 5; constexpr int kNumberOfSecondaryGridLines = 4; -constexpr KDCoordinate GraphView::k_stampSize; -constexpr uint8_t GraphView::k_mask[k_stampSize*k_stampSize]; GraphView::GraphView(FunctionStore * functionStore, AxisInterval * axisInterval) : #if GRAPH_VIEW_IS_TILED @@ -151,84 +150,133 @@ float GraphView::pixelToFloat(Axis axis, KDCoordinate p) const { return min(axis) + pixels*(max(axis)-min(axis))/pixelLength(axis); } -KDCoordinate GraphView::floatToPixel(Axis axis, float f) const { +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; - /* KDCoordinate does not support as big number as float. We thus cap the - * fraction before casting. */ - fraction = fraction > 2 ? 2.0f : fraction; - fraction = fraction < -1 ? -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 { - KDColor workingBuffer[k_stampSize*k_stampSize]; - for (int i=0; inumberOfActiveFunctions(); i++) { + 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); - float y = 0.0f; - float x = 0.0f; - for (KDCoordinate px = rect.x()-k_stampSize; pxevaluateAtAbscissa(x, m_evaluateContext); - KDCoordinate py = floatToPixel(Axis::Vertical, y); - stampAtLocation(px, py, f->color(), ctx, workingBuffer); - if (px > rect.x()-k_stampSize) { - jointDots(previousX, previousY, x, y, f, k_maxNumberOfIterations, ctx, workingBuffer); + 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(KDCoordinate px, KDCoordinate py, KDColor color, KDContext * ctx, KDColor * workingBuffer) const { +void GraphView::stampAtLocation(float pxf, float pyf, KDColor color, KDContext * ctx) const { // We avoid drawing when no part of the stamp is visible - if (px < -k_stampSize || px > pixelLength(Axis::Horizontal)+k_stampSize || py < -k_stampSize || py > pixelLength(Axis::Vertical)+k_stampSize) { + if (pyf < -stampSize || pyf > pixelLength(Axis::Vertical)+stampSize) { return; } - KDRect stampRect(px-k_stampSize/2, py-k_stampSize/2, k_stampSize, k_stampSize); - ctx->blendRectWithMask(stampRect, color, k_mask, workingBuffer); + 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, KDColor * workingBuffer) const { - KDCoordinate px = floatToPixel(Axis::Horizontal, x); - KDCoordinate pu = floatToPixel(Axis::Horizontal, u); - KDCoordinate py = floatToPixel(Axis::Vertical, y); - KDCoordinate pv = floatToPixel(Axis::Vertical, v); - if (py - k_stampSize/2 < pv && pv < py + k_stampSize/2) { +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; } - float middleX = (x + u)/2; - float middleY = function->evaluateAtAbscissa(middleX, m_evaluateContext); - KDCoordinate pMiddleX = floatToPixel(Axis::Horizontal, middleX); - KDCoordinate pMiddleY = floatToPixel(Axis::Vertical, middleY); - if ((y < middleY && middleY < v) || (v < middleY && middleY < u)) { + // 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 */ - straightJoinDots(px, py, pu, pv, function, ctx, workingBuffer); + 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(pMiddleX, pMiddleY, function->color(), ctx, workingBuffer); - jointDots(x, y, middleX, middleY, function, maxNumberOfRecursion-1, ctx, workingBuffer); - jointDots(middleX, middleY, u, v, function, maxNumberOfRecursion-1, ctx, workingBuffer); + 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(KDCoordinate px, KDCoordinate py, KDCoordinate pu, KDCoordinate pv, Function * function, KDContext * ctx, KDColor * workingBuffer) const { - if (py < pv) { - KDCoordinate pm = px; - KDCoordinate pVerticalMiddle = (py + pv)/2; - for (KDCoordinate pn = py; pn < pv; pn++) { - if (pn == pVerticalMiddle) { - pm = pu; - } - stampAtLocation(pm, pn, function->color(), ctx, workingBuffer); +void GraphView::straightJoinDots(float pxf, float pyf, float puf, float pvf, KDColor color, KDContext * ctx) const { + if (pyf < pvf) { + for (float pnf = pyf; pnf