Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,42 @@ void Canvas::setLineEnds(LineEnds value)
}


void Canvas::setLinePattern(LinePattern & value)
{
Primitive p;
p.cmd = PrimitiveCmd::SetLinePattern;
p.linePattern = value;
m_displayController->addPrimitive(p);
}


void Canvas::setLinePatternLength(uint8_t value)
{
Primitive p;
p.cmd = PrimitiveCmd::SetLinePatternLength;
p.ivalue = value;
m_displayController->addPrimitive(p);
}


void Canvas::setLinePatternOffset(uint8_t value)
{
Primitive p;
p.cmd = PrimitiveCmd::SetLinePatternOffset;
p.ivalue = value;
m_displayController->addPrimitive(p);
}


void Canvas::setLineOptions(LineOptions options)
{
Primitive p;
p.cmd = PrimitiveCmd::SetLineOptions;
p.lineOptions = options;
m_displayController->addPrimitive(p);
}


void Canvas::setBrushColor(RGB888 const & color)
{
Primitive p;
Expand Down
44 changes: 44 additions & 0 deletions src/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,50 @@ class Canvas {
*/
void setLineEnds(LineEnds value);

/**
* @brief Sets line pattern
*
* @param value Line pattern.
*
* Example:
*
* Canvas.setLinePattern(pattern)
*/
void setLinePattern(LinePattern & value);

/**
* @brief Sets line pattern length
*
* @param value Line pattern length.
*
* Example:
*
* Canvas.setLinePatternLength(length)
*/
void setLinePatternLength(uint8_t value);

/**
* @brief Sets offset position within the line pattern
*
* @param value offset position.
*
* Example:
*
* Canvas.setLinePatternOffset(position)
*/
void setLinePatternOffset(uint8_t value);

/**
* @brief Sets line drawing options
*
* @param value Line drawing options.
*
* Example:
*
* Canvas.setLineOptions(LineOptions().Antialias(true).Smooth(true));
*/
void setLineOptions(LineOptions value);

/**
* @brief Fills a single pixel with the pen color.
*
Expand Down
15 changes: 15 additions & 0 deletions src/displaycontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,9 @@ void IRAM_ATTR BitmappedDisplayController::resetPaintState()
m_paintState.absClippingRect = m_paintState.clippingRect;
m_paintState.penWidth = 1;
m_paintState.lineEnds = LineEnds::None;
m_paintState.linePattern = LinePattern();
m_paintState.lineOptions = LineOptions();
m_paintState.linePatternLength = 8;
}


Expand Down Expand Up @@ -818,6 +821,18 @@ void IRAM_ATTR BitmappedDisplayController::execPrimitive(Primitive const & prim,
case PrimitiveCmd::SetLineEnds:
paintState().lineEnds = prim.lineEnds;
break;
case PrimitiveCmd::SetLinePattern:
paintState().linePattern = prim.linePattern;
break;
case PrimitiveCmd::SetLinePatternLength:
paintState().linePatternLength = imin(64, imax(1, prim.ivalue));
break;
case PrimitiveCmd::SetLinePatternOffset:
paintState().linePattern.offset = prim.ivalue % paintState().linePatternLength;
break;
case PrimitiveCmd::SetLineOptions:
paintState().lineOptions = prim.lineOptions;
break;
}
}

Expand Down
115 changes: 99 additions & 16 deletions src/displaycontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ enum PrimitiveCmd : uint8_t {
// Set line ends
// params: lineEnds
SetLineEnds,

// Set line pattern
// params: linePattern
SetLinePattern,

// Set line pattern length
// params: ivalue
SetLinePatternLength,

// Set line pattern offset
// params: ivalue
SetLinePatternOffset,

// Set line options
// params: lineOptions
SetLineOptions,
};


Expand Down Expand Up @@ -623,6 +639,25 @@ struct PixelDesc {
} __attribute__ ((packed));


struct LinePattern {
uint8_t pattern[8] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
uint8_t offset = 0; // bit offset in pattern, automatically updated whilst drawing a line

void setPattern(const uint8_t newPattern[8]) {
for (int i = 0; i < 8; ++i) {
pattern[i] = newPattern[i];
}
}
} __attribute__ ((packed));


struct LineOptions {
bool usePattern = false;
bool omitFirst = false;
bool omitLast = false;
} __attribute__ ((packed));


struct Primitive {
PrimitiveCmd cmd;
union {
Expand All @@ -639,6 +674,8 @@ struct Primitive {
Path path;
PixelDesc pixelDesc;
LineEnds lineEnds;
LinePattern linePattern;
LineOptions lineOptions;
TaskHandle_t notifyTask;
} __attribute__ ((packed));

Expand All @@ -651,7 +688,7 @@ struct Primitive {
struct PaintState {
RGB888 penColor;
RGB888 brushColor;
Point position; // value already traslated to "origin"
Point position; // value already translated to "origin"
GlyphOptions glyphOptions;
PaintOptions paintOptions;
Rect scrollingRegion;
Expand All @@ -660,6 +697,9 @@ struct PaintState {
Rect absClippingRect; // actual absolute clipping rectangle (calculated when setting "origin" or "clippingRect")
int16_t penWidth;
LineEnds lineEnds;
LineOptions lineOptions;
LinePattern linePattern;
int8_t linePatternLength;
};


Expand Down Expand Up @@ -1091,32 +1131,59 @@ class GenericBitmappedDisplayController : public BitmappedDisplayController {
template <typename TPreparePixel, typename TRawFillRow, typename TRawSetPixel>
void genericAbsDrawLine(int X1, int Y1, int X2, int Y2, RGB888 const & color, TPreparePixel preparePixel, TRawFillRow rawFillRow, TRawSetPixel rawSetPixel)
{
if (paintState().penWidth > 1) {
absDrawThickLine(X1, Y1, X2, Y2, paintState().penWidth, color);
auto & pState = paintState();
if (pState.penWidth > 1) {
absDrawThickLine(X1, Y1, X2, Y2, pState.penWidth, color);
return;
}
auto & lineOptions = pState.lineOptions;
bool dottedLine = lineOptions.usePattern;
auto pattern = preparePixel(color);
if (Y1 == Y2) {
if (!dottedLine && (Y1 == Y2)) {
// horizontal line
if (Y1 < paintState().absClippingRect.Y1 || Y1 > paintState().absClippingRect.Y2)
if (Y1 < pState.absClippingRect.Y1 || Y1 > pState.absClippingRect.Y2)
return;
if (lineOptions.omitFirst) {
if (X1 < X2)
X1 += 1;
else
X1 -= 1;
}
if (lineOptions.omitLast) {
if (X1 < X2)
X2 -= 1;
else
X2 += 1;
}
if (X1 > X2)
tswap(X1, X2);
if (X1 > paintState().absClippingRect.X2 || X2 < paintState().absClippingRect.X1)
if (X1 > pState.absClippingRect.X2 || X2 < pState.absClippingRect.X1)
return;
X1 = iclamp(X1, paintState().absClippingRect.X1, paintState().absClippingRect.X2);
X2 = iclamp(X2, paintState().absClippingRect.X1, paintState().absClippingRect.X2);
X1 = iclamp(X1, pState.absClippingRect.X1, pState.absClippingRect.X2);
X2 = iclamp(X2, pState.absClippingRect.X1, pState.absClippingRect.X2);
rawFillRow(Y1, X1, X2, pattern);
} else if (X1 == X2) {
} else if (!dottedLine && (X1 == X2)) {
// vertical line
if (X1 < paintState().absClippingRect.X1 || X1 > paintState().absClippingRect.X2)
if (X1 < pState.absClippingRect.X1 || X1 > pState.absClippingRect.X2)
return;
if (lineOptions.omitFirst) {
if (Y1 < Y2)
Y1 += 1;
else
Y1 -= 1;
}
if (lineOptions.omitLast) {
if (Y1 < Y2)
Y2 -= 1;
else
Y2 += 1;
}
if (Y1 > Y2)
tswap(Y1, Y2);
if (Y1 > paintState().absClippingRect.Y2 || Y2 < paintState().absClippingRect.Y1)
if (Y1 > pState.absClippingRect.Y2 || Y2 < pState.absClippingRect.Y1)
return;
Y1 = iclamp(Y1, paintState().absClippingRect.Y1, paintState().absClippingRect.Y2);
Y2 = iclamp(Y2, paintState().absClippingRect.Y1, paintState().absClippingRect.Y2);
Y1 = iclamp(Y1, pState.absClippingRect.Y1, pState.absClippingRect.Y2);
Y2 = iclamp(Y2, pState.absClippingRect.Y1, pState.absClippingRect.Y2);
for (int y = Y1; y <= Y2; ++y)
rawSetPixel(X1, y, pattern);
} else {
Expand All @@ -1131,18 +1198,34 @@ class GenericBitmappedDisplayController : public BitmappedDisplayController {
// https://github.com/ktfh/ClippedLine/blob/master/clip.hpp
// For now Sutherland-Cohen algorithm is only used to check the line is actually visible,
// then test for every point inside the main Bresenham's loop.
if (!clipLine(X1, Y1, X2, Y2, paintState().absClippingRect, true)) // true = do not change line coordinates!
if (!clipLine(X1, Y1, X2, Y2, pState.absClippingRect, true)) // true = do not change line coordinates!
return;
auto & linePattern = pState.linePattern;
auto linePatternLength = pState.linePatternLength;
const int dx = abs(X2 - X1);
const int dy = abs(Y2 - Y1);
const int sx = X1 < X2 ? 1 : -1;
const int sy = Y1 < Y2 ? 1 : -1;
int err = (dx > dy ? dx : -dy) / 2;
bool omittingFirst = lineOptions.omitFirst;
bool omittingLast = lineOptions.omitLast;
bool drawPixel = !omittingFirst;
while (true) {
if (paintState().absClippingRect.contains(X1, Y1)) {
bool ending = X1 == X2 && Y1 == Y2;
if (dottedLine) {
if (!omittingFirst && !(ending && omittingLast)) {
drawPixel = getBit(linePattern.pattern, linePattern.offset);
linePattern.offset = (linePattern.offset + 1) % linePatternLength;
} else {
drawPixel = false;
}
}
if (drawPixel && pState.absClippingRect.contains(X1, Y1)) {
rawSetPixel(X1, Y1, pattern);
}
if (X1 == X2 && Y1 == Y2)
if (omittingFirst)
omittingFirst = false;
if (ending)
break;
int e2 = err;
if (e2 > -dx) {
Expand Down
7 changes: 7 additions & 0 deletions src/fabutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,13 @@ Rect IRAM_ATTR Rect::intersection(Rect const & rect) const
}


bool getBit(uint8_t* array, size_t bitIndex) {
size_t byteIndex = bitIndex / 8;
int bitPosition = 7 - (bitIndex % 8);
return (array[byteIndex] >> bitPosition) & 1;
}


///////////////////////////////////////////////////////////////////////////////////
// rgb222_to_hsv
// R, G, B in the 0..3 range
Expand Down
3 changes: 3 additions & 0 deletions src/fabutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ T moveItems(T dest, T src, size_t n)
}


bool getBit(uint8_t* array, size_t bitIndex);


void rgb222_to_hsv(int R, int G, int B, double * h, double * s, double * v);


Expand Down