Bodmer / TFT_eFEX

A support library for TFT_eSPI that adds commonly used extra functions
Other
83 stars 27 forks source link

Use _tft to calculate viewport width/height #16

Closed tobozo closed 4 years ago

tobozo commented 4 years ago

followup for #13

thanks for the insight on the user setup, I mostly use Arduino so the best I've come up with is defining pin values before including TFT_eSPI and also defining #USER_SETUP_DONE to prevent further loading of any default setup, but I only found this out after losing a dozen profiles :man_facepalming:

re: pixels on the small circles, I agree it could be nicer by drawing circles but the example is about demonstrating line gradients, that unperfect disc drawing is there do show the features of drawGradientLine, and that includes the limitations of the drawLine algorithm.

In the pie chart example you mention, the fillSegment() function uses fillTriangle(). Problem: fillTriangle() only uses horizontal lines for its rendering and I want to demonstrate gradientLines in all directions Only one gradient direction is too limitative to make it worth writing a fillGradientTriangle() function, and I don't feel confident enough to create the missing parts.

I'll dig into this repo to see what I can find but I suspect the optimizations also tend to single direction and prevent code re-use.

Bodmer commented 4 years ago

OK, thanks for this.

I am still not keen on an example that shows missing pixels, from a users viewpoint these are undesirable and they will have little interest in how the effect is achieved. I think missing pixels can be avoided. It would be great to have a gradient implementation very similar (or identical) to the ucglib library as documented here.

Bodmer commented 4 years ago

I've created a simple equivalent to ucglib rectangular gradient that uses the fast alphaBlend function (you will need smooth fonts enabled in setup) built into TFT_eSPI for the interpolation between colours:

/* Draw filled rectangle with a 4 colour (c0, c1, c2, c3) gradient
 * The 4 colours are at the corners of the rectangle as shown below:
 * 
 *      c0             c1
 *       +------W------+
 *       |             |
 *       H             | Rectangle of width W and height H
 *       |             |
 *       +-------------+ 
 *      c2             c3
 * 
 * If c0 = c1 and c2 = c3 a vertical gradient is drawn
 * If c0 = c2 and c1 = c3 a horizontal gradient is drawn
 */

#include <TFT_eSPI.h>
TFT_eSPI    tft = TFT_eSPI();         // Declare object "tft"

uint16_t cornerCol[4];
bool boot = true;

void setup() {
  Serial.begin(115200);
  tft.init();
  tft.fillScreen(TFT_BLACK);
  tft.setSwapBytes(true);

  // Colour combinations for testing
  cornerCol[0] = TFT_RED;
  cornerCol[1] = TFT_GREEN;
  cornerCol[2] = TFT_MAGENTA;
  cornerCol[3] = TFT_CYAN;

/*
  cornerCol[0] = TFT_BLACK;
  cornerCol[1] = TFT_GREEN;
  cornerCol[2] = TFT_BLACK;
  cornerCol[3] = TFT_BLACK;
//*/
/*
  cornerCol[0] = TFT_GREEN;
  cornerCol[1] = TFT_GREEN;
  cornerCol[2] = TFT_BLACK;
  cornerCol[3] = TFT_BLACK;
//*/
/*
  cornerCol[0] = TFT_GREEN;
  cornerCol[1] = TFT_BLACK;
  cornerCol[2] = TFT_BLACK;
  cornerCol[3] = TFT_GREEN;
//*/
/*
  cornerCol[0] = TFT_RED;
  cornerCol[1] = TFT_GREEN;
  cornerCol[2] = TFT_GREEN;
  cornerCol[3] = TFT_RED;
//*/
}

void loop() {

  uint16_t box_x = 40;
  uint16_t box_y = 40;
  uint16_t box_w = 160;
  uint16_t box_h = 160;

  uint16_t pbuf[box_w]; // Line buffer

  for (uint16_t y = 0; y < box_h; y++) {
    uint8_t yr = map(y, 0, box_h, 0, 255);
    uint16_t r = tft.alphaBlend(yr, cornerCol[3], cornerCol[1]);
    uint16_t l = tft.alphaBlend(yr, cornerCol[2], cornerCol[0]);
    for (uint16_t x = 0; x < box_w; x++) {

      uint8_t xr = map(x, 0, box_w, 0, 255);
      uint16_t p = tft.alphaBlend(xr, r, l);
      pbuf[x] = p;
      //tft.drawPixel(box_x + x, box_y + y, p); // Pixel by pixel (slow!)
    }
    tft.pushImage(box_x, box_y + y, box_w, 1, pbuf);
  }

  if (boot) { delay(9000); boot = false; } // Longer delay for testing combinations
  delay(1000);

  cornerCol[0] = tft.color565(random(0x100), random(0x100), random(0x100) );
  cornerCol[1] = tft.color565(random(0x100), random(0x100), random(0x100) );
  cornerCol[2] = tft.color565(random(0x100), random(0x100), random(0x100) );
  cornerCol[3] = tft.color565(random(0x100), random(0x100), random(0x100) );
}

I think the alphaBlend() function should be available with smooth fonts disabled so I will move it to the main TFT_eSPI code block.

Bodmer commented 4 years ago

I am closing this as I suspect this push will not be progressing... I will add a gradient feature at some point but I think a line based gradient has few practical applications. Thanks for the idea,

Bodmer commented 4 years ago

I thought I would write a sketch that used a 1 bit per pixel Sprite as a mask, this opens up new possibilities for colour graded arbitrary shapes: screenshot_2019_12_15_16_27_31

In case you are interested, here is my concept test sketch used for the above screen capture:

/* Draw filled rectangle with a 4 colour (c0, c1, c2, c3) gradient
 * The 4 colours are at the corners of the rectangle as shown below:
 * 
 *      c0             c1
 *       +------W------+
 *       |             |
 *       H             | Rectangle of width W and height H
 *       |             |
 *       +-------------+ 
 *      c2             c3
 * 
 * If c0 = c1 and c2 = c3 a vertical gradient is drawn
 * If c0 = c2 and c1 = c3 a horizontal gradient is drawn
 * 
 * This example then uses a 1 bit per pixel Sprite as a mask, this
 * enables arbitrary shapes to be drawn with a colour gradient.
 */

#include <TFT_eSPI.h>
TFT_eSPI    tft = TFT_eSPI();         // Declare object "tft"
TFT_eSprite spr = TFT_eSprite(&tft);  // Declare Sprite object "spr" with pointer to "tft" object

uint16_t cornerCol[4];
bool boot = true;

void setup() {
  Serial.begin(115200);
  tft.init();

  // Set colour depth to 1
  spr.setColorDepth(1);

  tft.fillScreen(TFT_BLACK);

  //tft.setSwapBytes(true); // To check colour contour shape
  //tft.initDMA(); // DMA testing only

  // Colour combinations for testing
  cornerCol[0] = TFT_RED;
  cornerCol[1] = TFT_GREEN;
  cornerCol[2] = TFT_MAGENTA;
  cornerCol[3] = TFT_CYAN;
}

#define LBUF_SIZE 128  // pixel buffer

void loop() {

  uint16_t box_x = 0;
  uint16_t box_y = 0;
  uint16_t box_w = 160;
  uint16_t box_h = 100;

  uint16_t* pbuf1 = (uint16_t*)malloc( LBUF_SIZE * 2 ); // Line buffer
  uint16_t* pbuf2 = (uint16_t*)malloc( LBUF_SIZE * 2);  // Line buffer
  uint16_t* bufPtr[2] = { pbuf1, pbuf2 };               // Save copy of pointer for toggle buffer action
  uint16_t  pCount = 0;    // Pixel count
  bool      buf1   = true; // Buffer toggle flag

  // Create a sprite of defined size
  spr.createSprite(box_w, box_h); // Automatically cleared to 0 on creation (all masked)

  // Cut a shape in mask with colour = 1
  spr.fillTriangle(box_w, box_h, 0, box_h/2, box_w/2, 0, 1);
  spr.fillEllipse(box_w/2, box_h/2, box_w/2-1, box_h/2-1, 1);

  // Add some masked text (then background shows through text)
  spr.setTextDatum(MC_DATUM);
  spr.setTextColor(0);
  spr.drawString("Season's", box_w/2,(box_h/2)-16,4);
  spr.drawString("Greetings",box_w/2,(box_h/2)+16,4);

  uint32_t dt = millis();

  tft.startWrite();

  for (uint16_t y = 0; y < box_h; y++) {
    uint8_t yr = map(y, 0, box_h, 0, 255);
    uint16_t r = tft.alphaBlend(yr, cornerCol[3], cornerCol[1]);
    uint16_t l = tft.alphaBlend(yr, cornerCol[2], cornerCol[0]);
    bool wmove = true;
    uint16_t xs = 0;
    pCount = 0;

    for (uint16_t x = 0; x < box_w; x++) {
      uint8_t xr = map(x, 0, box_w, 0, 255);
      uint16_t p = tft.alphaBlend(xr, r, l);
      // Draw only pizels that are set to 1 in Sprite
      if (spr.readPixel(x,y)) {
        *(bufPtr[buf1] + pCount++) = p<<8 | p>>8;
        if (wmove) { xs = x; wmove = false; }
        if (pCount >= LBUF_SIZE) wmove = true;
      }
      else wmove = true; // Window has to be moved

      if (wmove && pCount) {
        tft.pushImage(xs, y, pCount, 1, bufPtr[buf1]);
        //tft.pushImageDMA(xs, y, pCount, 1, bufPtr[buf1]);
        //buf1 = !buf1; // Toggle buffer (for DMA)
        pCount = 0;
        xs = x+1;
        wmove = false;
      }
    }

    if (pCount) { // Finish drawing any remaining pixels
      tft.pushImage(xs, y, pCount, 1, bufPtr[buf1]);      // 32ms
      //tft.pushImageDMA(xs, y, pCount, 1, bufPtr[buf1]); // 24ms
      //buf1 = !buf1; // Toggle buffer (for DMA)
    }
  }
  tft.endWrite(); // This waits until DMA is finished

  free(pbuf1);
  free(pbuf2);

  spr.deleteSprite();

  Serial.println(millis()-dt);

  //screenServer();

  if (boot) { delay(4500); boot = false; } // Longer delay for testing fixed combinations
  delay(500);

  // Random corner colours
  cornerCol[0] = tft.color565(random(0x100), random(0x100), random(0x100) );
  cornerCol[1] = tft.color565(random(0x100), random(0x100), random(0x100) );
  cornerCol[2] = tft.color565(random(0x100), random(0x100), random(0x100) );
  cornerCol[3] = tft.color565(random(0x100), random(0x100), random(0x100) );
}

Edit: In my haste I forgot to free the buffer, so sketch crashes eventually, I have just updated it!

tobozo commented 4 years ago

Sorry for the silence, I got totally carried away by other projects :-)

I had time to explore the alphaBlend function and test your suggestions though. AlphaBlend is heavily used in this MPU9050 demo.

And now I totally agree that drawGradientLine is quite useles.

Kinda offtopic: during those experiments I also observed that in some situations spiram can considerably slow down sprite rendering. So I came up with this patch to let the developer choose whether a given sprite will use spiram or not.

Please let me know if you need a PR for that.

I'll close this PR now it's become obvious it wasn't necessary in the first place ^^