[apps/graph/graph] Draw curves with antialiazing

Change-Id: Ica25684001e993d1c8946f12ccf40725eba7ceb2
This commit is contained in:
Émilie Feral
2016-11-22 14:59:54 +01:00
parent 5fbf4d5073
commit b7a8b692eb
2 changed files with 105 additions and 62 deletions

View File

@@ -1,5 +1,6 @@
#include "graph_view.h"
#include <assert.h>
#include <math.h>
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; i<m_functionStore->numberOfActiveFunctions(); 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; px<rect.right(); px++) {
float previousX = x;
x = pixelToFloat(Axis::Horizontal, px);
float previousY = y;
y = f->evaluateAtAbscissa(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; i<stampSize; i++) {
for (int j=0; j<stampSize; j++) {
shiftedMask[i][j] = dx * (stampMask[i][j]*dy+stampMask[i+1][j]*(1.0f-dy))
+ (1.0f-dx) * (stampMask[i][j+1]*dy + stampMask[i+1][j+1]*(1.0f-dy));
}
}
KDRect stampRect(px-circleDiameter/2, py-circleDiameter/2, stampSize, stampSize);
ctx->blendRectWithMask(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<pvf; pnf+= 1.0f) {
float pmf = pxf + (pnf - pyf)*(puf - pxf)/(pvf - pyf);
stampAtLocation(pmf, pnf, color, ctx);
}
return;
}
straightJoinDots(pu, pv, px, py, function, ctx, workingBuffer);
straightJoinDots(puf, pvf, pxf, pyf, color, ctx);
}
#if 0

View File

@@ -34,13 +34,6 @@ public:
void setContext(Context * evaluateContext);
Context * context() const;
private:
constexpr static int k_maxNumberOfIterations = 10;
constexpr static KDCoordinate k_stampSize = 3;
constexpr static uint8_t k_mask[k_stampSize*k_stampSize] = {
0xff, 0x73, 0xff,
0x73, 0x00, 0x73,
0xff, 0x73, 0xff,
};
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
@@ -55,7 +48,7 @@ private:
KDCoordinate pixelLength(Axis axis) const;
float pixelToFloat(Axis axis, KDCoordinate p) const;
KDCoordinate floatToPixel(Axis axis, float f) const;
float floatToPixel(Axis axis, float f) const;
void drawLine(KDContext * ctx, KDRect rect, Axis axis,
float coordinate, KDColor color, KDCoordinate thickness = 1) const;
@@ -66,11 +59,13 @@ private:
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, KDColor * workingBuffer) const;
/* Join two dots with a straight line (changing abscissa halfway). */
void straightJoinDots(KDCoordinate px, KDCoordinate py, KDCoordinate pu, KDCoordinate pv, Function * function, KDContext * ctx, KDColor * workingBuffer) const;
/* Stamp centered around (px, py). */
void stampAtLocation(KDCoordinate px, KDCoordinate py, KDColor color, KDContext * ctx, KDColor * workingBuffer) const;
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;
#if GRAPH_VIEW_IS_TILED
static constexpr KDCoordinate kTileWidth = 32;
static constexpr KDCoordinate kTileHeight = 32;