Bodmer / TFT_ILI9341

A fast Arduino IDE compatible graphics and fonts library including a driver for the ILI9341 based TFT displays.
109 stars 32 forks source link

Stray pixels when overwriting lines with FAST_LINE enabled #24

Open MakerMatrix opened 3 years ago

MakerMatrix commented 3 years ago

I have a code that scrolls a real-time xy scatter plot. I only update the lines in the scatter plot, for speed (love your library for this - it made that POSSIBLE in the first place). i.e. I do not erase the screen and redraw them...

What I found is that when I redraw the lines in the background color, maybe 2-3% of the time it leaves a stray pixel of the old line behind. It could in fact be more frequent than that, because in many (maybe most) cases the new line segments would write over the stray pixels anyway. But that's what the casual user would notice in my charting app.

This never occurs if I comment out #define FAST_LINE in User_Setup.h

It feels a little like some kind of an off by one error but that is just a gut feeling. Have you seen this?

MakerMatrix commented 3 years ago

Here is the code snippet. The code path in question is when scrolling == true (that's when the previous lines get overwritten by the same line drawn in background color).

// Check if the queue is full
      if (fQ.isFull())
      {
        scrolling = true;
        fQ.pop(&p0);                                        // Pop the oldest point
        fQ.peek(&p1);                                       // Peek at the new oldest point -> New xMin
        dx = p1.x - p0.x;                                   // We will move the axis right by dx, remember it
        xyChart.setAxisLimitsX(p1.x, xyChart.xMax + dx, 6); // New X axis limits run from oldest point to xMax+dx
      }
      else
      {
        fQ.peek(&p0);       // or peek the oldest point -> "New" xMin
        fQ.peekIdx(&p1, 1); // and peek at the second oldest point
        dx = 0;
      }

      fQ.push(&p); // Update the queue with latest value
      xPrev = p.x;
      yPrev = p.y;

      if (scrolling)
      { // Erase the line between between two oldest points.
        xyChart.eraseLine(tft, p0.x + dx, p0.y, p1.x + dx, p1.y);
        // Redraw the axes/labels
        xyChart.drawAxisX(tft, 10);
        xyChart.drawLabelsX(tft);
        xyChart.drawAxisY(tft, 10);
        xyChart.drawY0(tft);
      }
      // Now move lines one by one (looks SO much better than clearing/drawing, but bookeeping...)
      for (i = 1; i <= fQLen; i++)
      {
        fQ.peekIdx(&p0, i - 1);                                   // Peek at tail of line to move
        fQ.peekIdx(&p1, i);                                       // Peek at head of line to move
        xyChart.eraseLine(tft, p0.x + dx, p0.y, p1.x + dx, p1.y); // erase it at the old x-axis limits
        xyChart.drawLine(tft, p0.x, p0.y, p1.x, p1.y);            // draw it at the current x-axis limits
      }
      if (noise) // Wait here for noise to decay (after the graph is drawn) - it looks prettier.
      {
        delay(300);
        noise = false;
      }
MakerMatrix commented 3 years ago

And the eraseLine and drawLine methods, which just transform from chart coords (float) back to pixel coords.

/ Draw a line
void ChartXY::drawLine(TFT_ILI9341 &tft, float x0, float y0, float x1, float y1)
{
    uint16_t px0, py0, px1, py1;

    px0 = round((x0 - xMin) * xPxPerX + xPxLo);
    py0 = round((yMin - y0) * yPxPerY + yPxHi);
    px1 = round((x1 - xMin) * xPxPerX + xPxLo);
    py1 = round((yMin - y1) * yPxPerY + yPxHi);

    tft.drawLine(px0, py0, px1, py1, lineColor);
    // tft.drawLine(px0+1, py0+1, px1+1, py1+1, color);
}

// Erase a line
void ChartXY::eraseLine(TFT_ILI9341 &tft, float x0, float y0, float x1, float y1)
{
    uint16_t px0, py0, px1, py1;

    px0 = round((x0 - xMin) * xPxPerX + xPxLo);
    py0 = round((yMin - y0) * yPxPerY + yPxHi);
    px1 = round((x1 - xMin) * xPxPerX + xPxLo);
    py1 = round((yMin - y1) * yPxPerY + yPxHi);

    tft.drawLine(px0, py0, px1, py1, chartBGColor);
}