diff --git a/src/canvas.cpp b/src/canvas.cpp index e7d3440c..a80fa285 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -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; diff --git a/src/canvas.h b/src/canvas.h index 43553047..6c41d722 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -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. * diff --git a/src/displaycontroller.cpp b/src/displaycontroller.cpp index 1119afee..93d87a42 100644 --- a/src/displaycontroller.cpp +++ b/src/displaycontroller.cpp @@ -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; } @@ -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; } } diff --git a/src/displaycontroller.h b/src/displaycontroller.h index 17b3dad3..f8ed4857 100644 --- a/src/displaycontroller.h +++ b/src/displaycontroller.h @@ -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, }; @@ -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 { @@ -639,6 +674,8 @@ struct Primitive { Path path; PixelDesc pixelDesc; LineEnds lineEnds; + LinePattern linePattern; + LineOptions lineOptions; TaskHandle_t notifyTask; } __attribute__ ((packed)); @@ -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; @@ -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; }; @@ -1091,32 +1131,59 @@ class GenericBitmappedDisplayController : public BitmappedDisplayController { template 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 { @@ -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) { diff --git a/src/fabutils.cpp b/src/fabutils.cpp index 5dac4368..9f6a3443 100644 --- a/src/fabutils.cpp +++ b/src/fabutils.cpp @@ -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 diff --git a/src/fabutils.h b/src/fabutils.h index e5a9b098..7e8c8418 100644 --- a/src/fabutils.h +++ b/src/fabutils.h @@ -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);