Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

push a large sprite to a smaller window #712

Closed
dede53 opened this issue Jul 31, 2020 · 13 comments
Closed

push a large sprite to a smaller window #712

dede53 opened this issue Jul 31, 2020 · 13 comments

Comments

@dede53
Copy link

dede53 commented Jul 31, 2020

Hey Bodmer,

Your lib is fantastic! I'm using it a lot, thank you very much!

Now I have a question and could not find a solution yet:

I have a sprite higher then my display and I can "scroll" the sprite up and down by pushing the sprite to different positions. Works really fine!
Now I want to have a small part on the bottom of my display where the sprite is not affecting the content (like a fixed footer). As far as I know there is no method to set a window where the sprite is pushed to. How could I solve my problem?

          +------------+ <- sprite-frame moving up/down 
          |            |
          |            |
+---------|------------|------+ <- display-frame
|         |            |      |
|         |            |      |
|#########|%%%%%%%%%%%%|######|<- fixed rect
+---------|------------|------+
          |            |
          |            |
          +------------+

The %%% mark the part with which I have the difficulties. The ### are logically not affected by scrolling.
I already tried with the scroll-method, but as soon as I change the scroll direction i have to repaint the content which scrolled already out of the display. I tried a lot, but I did not get it to work (different scroll speed makes it really difficult to calculate the position where the content has to be written).

@dede53 dede53 changed the title push a large sprite to a small window push a large sprite to a smaller window Jul 31, 2020
@dede53
Copy link
Author

dede53 commented Aug 1, 2020

I found in another issue the possibility to use the Sprite_Scroll_16Bit example as follows:

// Size of sprite image for the scrolling text, this requires ~14 Kbytes of RAM
#define IWIDTH  240
#define IHEIGHT 90

// Pause in milliseconds to set scroll speed
#define WAIT 0

#include <TFT_eSPI.h>                 // Include the graphics library (this includes the sprite functions)

TFT_eSPI    tft = TFT_eSPI();         // Create object "tft"

TFT_eSprite img = TFT_eSprite(&tft);  // Create Sprite object "img" with pointer to "tft" object
//                                    // the pointer is used by pushSprite() to push it onto the TFT

// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------
void setup(void) {
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLUE);
}
void loop() {
    // Create the sprite and clear background to black
    img.createSprite(IWIDTH, IHEIGHT);

    for (int pos = 0; pos > -70; pos--){
      build_banner("TFT_eSPI sprite" , pos);
      img.pushSprite(20, 50);
      yield();
    }
    for (int pos = -70; pos < 0; pos++){
      build_banner("TFT_eSPI sprite" , pos);
      img.pushSprite(20, 50);
      yield();
    }
    // Delete sprite to free up the memory
    img.deleteSprite();
}

// #########################################################################
// Build the scrolling sprite image from scratch, draw text at x = xpos
// #########################################################################

void build_banner(String msg, int pos){
  img.fillSprite(TFT_WHITE); // Optional here as we fill the sprite later anyway
  
  // Now print text on top of the graphics
  img.setTextSize(1);           // Font size scaling is x1
  img.setTextFont(4);           // Font 4 selected
  img.setTextColor(TFT_BLACK);  // Black text, no background colour
  img.setTextWrap(false);       // Turn of wrap so we can print past end of sprite

  // Need to print twice so text appears to wrap around at left and right edges
  for(int i = 0; i < 5; i++){
    img.setCursor(6, pos + 25*i);
    img.print(msg); 
  }
}

With this example i can scroll up and down in a smooth way inside the sprite. But as soon as i do it in my sketch the same way, it is very slow. :(

@Bodmer
Copy link
Owner

Bodmer commented Aug 1, 2020

Thanks for raising this. I think a sprite pushWindow function would be relatively easy to add. ie:

spr.pushWindow(tx, ty, sx, sy, sw, sh);

Where:
tx, ty is the position on the TFT of the top left corner of the plotted sprite portion
sx, sy is the position of the top left corner of a window area within the sprite
sw, sh are the width and height of the window area within the sprite

There is already a function called setWindow that does all the bounds checks and can be used in this new function. The code for rendering a cropped sprite also exists so it is a case of bringing these together in a single function.

What do you think?

@dede53
Copy link
Author

dede53 commented Aug 2, 2020

That sounds fantastic!
Would be really nice to have it!

@dede53
Copy link
Author

dede53 commented Aug 2, 2020

I started to implement, but now i don't now further.
The following code is pushing the sprite only to a small window, but the content is not yet working.
In the out commented for loops i wanted to store the content, but i get a error allocating such a big part. I guess there is an easier way :)

void     pushWindow(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh);
/***************************************************************************************
** Function name:           pushWindow
** Description:             Push a part of the Sprite to the Display 
***************************************************************************************/
void TFT_eSprite::pushWindow(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh){
  if (!_created) return;

  _tft->setWindow(sx, sy, sw + sx, sh + sy);

    // uint8_t* data = (uint8_t*)calloc(sw * sh + 1, sizeof(uint8_t));;
    // for(int i = 0; i < sh; i++){
    //     for(int j = 0; j < sw; j++){
    //         *data++ = *(_img8 + i + j);
    //     }
    // }


  if (_bpp == 16)
  {
    bool oldSwapBytes = _tft->getSwapBytes();
    _tft->setSwapBytes(false);
    _tft->pushImage(tx, ty, sw, sh, _img + sy * _iwidth );
    _tft->setSwapBytes(oldSwapBytes);
  }
  else if (_bpp == 4)
  {
    _tft->pushImage(tx, ty, sw, sh, _img4 + sy * _iwidth, false, _colorMap);
  }
  else
    _tft->pushImage(tx, ty, sw, sh, _img8 + sy * _iwidth, false);
}

@Bodmer
Copy link
Owner

Bodmer commented Aug 2, 2020

I think pushSprite is a better function name as it is a similar function to that existing:

/***************************************************************************************
** Function name:           pushSprite
** Description:             Push a cropped sprite to the TFT at tx, ty
***************************************************************************************/
bool TFT_eSprite::pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh)
{
  if (!_created) return false;

  // Perform window boundary checks and crop if needed
  setWindow(sx, sy, sx + sw - 1, sy + sh - 1);

  /* These global variables are now populated for the sprite
  _xs = x start coordinate
  _ys = y start coordinate
  _xe = x end coordinate (inclusive)
  _ye = y end coordinate (inclusive)
  */

  // Calculate new sprite window bounding box width and height
  sw = _xe - _xs + 1;
  sh = _ye - _ys + 1;

  if (_bpp == 16)
  {
    bool oldSwapBytes = _tft->getSwapBytes();
    _tft->setSwapBytes(false);

    // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img + _iwidth * _ys );
    else // Render line by line
      while (sh--)
        _tft->pushImage(tx, ty++, sw, 1, _img + _xs + _iwidth * _ys++ );

    _tft->setSwapBytes(oldSwapBytes);
  }
  else if (_bpp == 8)
  {
    // To do
  }
  else if (_bpp == 4)
  {
    // To do
  }
  else if (_bpp == 1 )
  {
    // To do
  }

  return true;
}

You will need to add a new function prototype to TFT_eSPI.h

           // Push a windowed area of the sprite to the TFT at tx, ty
  bool     pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh);

I will expand this function for other colour depths and may just create an overloaded version of pushSprite with extra optional parameters.

@Bodmer
Copy link
Owner

Bodmer commented Aug 2, 2020

I think a "wrap" feature would also be good to have in this function via an optional boolean, viz:

bool     pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh, bool wrap = false);

Creating a spinner menu, magnetic compass, or "fruit/slot machine" rotating wheel type display would then be easy.

@dede53
Copy link
Author

dede53 commented Aug 2, 2020

The wrap feature sounds really interesting, haven't thought about that possibility yet.
I added to your code the other colour depths. Maybe it is usefull :)

/***************************************************************************************
** Function name:           pushSprite
** Description:             Push a cropped sprite to the TFT at tx, ty
***************************************************************************************/
void TFT_eSprite::pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh){
  if (!_created) return;

  // Perform window boundary checks and crop if needed
  setWindow(sx, sy, sx + sw - 1, sy + sh - 1);

   // Calculate new sprite window bounding box width and height
  sw = _xe - _xs + 1;
  sh = _ye - _ys + 1;

  if (_bpp == 16)
  {
    bool oldSwapBytes = _tft->getSwapBytes();
    _tft->setSwapBytes(false);

    // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img + _iwidth * _ys);
    else // Render line by line
      while (sh--)
        _tft->pushImage(tx, ty++, sw, 1, _img + _xs + _iwidth * _ys++);

    _tft->setSwapBytes(oldSwapBytes);
  }
  else if (_bpp == 4)
  {
    // To do
    // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img4 + _iwidth * _ys, false, _colorMap);
    else // Render line by line
      while (sh--)
        _tft->pushImage(tx, ty++, sw, 1, _img4 + _xs + _iwidth * _ys++, false, _colorMap);
  }
  else
  {
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img8 + sy * _iwidth, (bool)(_bpp == 8));
    else
      while(sh--)
        _tft->pushImage(tx, ty++, sw, 1, _img8 + _xs + _iwidth * _ys++, (bool)(_bpp == 8));
  }
}

For now I have to thank you a lot for the support and all your work!
It is famous to see the solution working! 🥇

@toetoast
Copy link

toetoast commented Aug 2, 2020

Just wondering if there would be any value in having the following as optional parameters?

  • Transparency value. Any pixel values matching this would be ignored.
  • The target destination (for the Push) can be overridden by a Sprite instance, instead of being sent to the TFT display. That way Sprite to Sprite merging is then possible. With the above Transparancy option, this might offer some interesting possibilities.

Tony

@Bodmer
Copy link
Owner

Bodmer commented Aug 3, 2020

The suggested code only works for special cases for 4bpp (x start and x end must always be even) and for 1bpp (x start and x end must be integer multiples of 8).

This code should work without these constraints but has not been fully tested yet:

/***************************************************************************************
** Function name:           pushSprite
** Description:             Push a cropped sprite to the TFT at tx, ty
***************************************************************************************/
bool TFT_eSprite::pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh)
{
  if (!_created) return false;

  // Perform window boundary checks and crop if needed
  setWindow(sx, sy, sx + sw - 1, sy + sh - 1);

  /* These global variables are now populated for the sprite
  _xs = x start coordinate
  _ys = y start coordinate
  _xe = x end coordinate (inclusive)
  _ye = y end coordinate (inclusive)
  */

  // Calculate new sprite window bounding box width and height
  sw = _xe - _xs + 1;
  sh = _ye - _ys + 1;

  if (_bpp == 16)
  {
    bool oldSwapBytes = _tft->getSwapBytes();
    _tft->setSwapBytes(false);

    // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img + _iwidth * _ys );
    else // Render line by line
      while (sh--)
        _tft->pushImage(tx, ty++, sw, 1, _img + _xs + _iwidth * _ys++ );

    _tft->setSwapBytes(oldSwapBytes);
  }
  else if (_bpp == 8)
  {
    // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img + _iwidth * _ys, true );
    else // Render line by line
    while (sh--)
      _tft->pushImage(tx, ty++, sw, 1, _img8 + _xs + _iwidth * _ys++, true );
  }
  else if (_bpp == 4)
  {
    // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img4 + _iwidth * _ys, false, _colorMap );
    else // Render line by line
    {
      uint32_t ds = _xs&1; // Odd x start pixel

      uint32_t de = 0;     // Odd x end pixel
      if ((sw > ds) && (_xe&1)) de = 1;

      uint32_t dm = 0;     // Midsection pixel count
      if (sw > (ds+de)) dm = sw - ds - de;
      sw--;

      uint32_t yp = _xs + ds + _iwidth * _ys;
      _tft->startWrite();
      while (sh--)
      {
        if (ds) _tft->drawPixel(tx, ty, readPixel(_xs, _ys) );
        if (dm) _tft->pushImage(tx + ds, ty, dm, 1, _img4 + (yp>>1), false, _colorMap );
        if (de) _tft->drawPixel(tx + sw, ty, readPixel(_xe, _ys) );        
        ty++;
        yp += _iwidth;
      }
      _tft->endWrite();
    }
  }
  else // 1bpp
  {
        // Check if a faster block copy to screen is possible
    if ( sx == 0 && sw == _iwidth)
      _tft->pushImage(tx, ty, sw, sh, _img4 + _iwidth * _ys, false );
    else // Render line by line
    {
      _tft->startWrite();
      _tft->setWindow(tx, ty, tx+sw-1, ty+sh-1);
      while (sh--)
      {
        for (uint32_t dx = _xs; dx < _xs + sw; dx++) _tft->pushColor(readPixel(dx, _ys));
        ty++;
        _ys++;
      }
      _tft->endWrite();
    }
  }

  return true;
}

@dede53
Copy link
Author

dede53 commented Aug 3, 2020

Hey!
In the following sketch I create a sprite with width 50, but _iwidth inside the lib is 56. This affects the option to use the fast copy where x == 0 && sw == _iwidth is checked. I don't know where the problem is, maybe only a little offset in the check is missing. Edit: In another sketch I could not reproduce this.
In addition I found out, that with a 1bit sprite the faster block copy is not working for me yet.
By calling it in a for loop counting up, the sprite is not moving pixel by pixel, but about 8 -10 pixel at once.

And here the sketch I used (click to expand)
#include <TFT_eSPI.h>                 // Include the graphics library (this includes the sprite functions)
TFT_eSPI    tft = TFT_eSPI();         // Create object "tft"
TFT_eSprite sprite = TFT_eSprite(&tft);  // Create Sprite object "img" with pointer to "tft" object
//                                    // the pointer is used by pushSprite() to push it onto the TFT



void setup() {
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLUE);
  sprite.setColorDepth(1);
  sprite.setTextDatum(MC_DATUM);
  sprite.createSprite(50, 100); // Narrow and tall
  for(int i = 1; i <=10; i++){
      sprite.drawString("Hey!", 6, (8 * i), 1);
  }
}

void loop() {
  for(int i = 0; i < 10; i++){
      sprite.pushSprite(60, 60, 0, i, 56, 50);
      delay(500);
  }
  for(int i = 10; i > 0; i--){
      sprite.pushSprite(60, 60, 0, i, 56, 50);
      delay(500);
  }
}

@Bodmer
Copy link
Owner

Bodmer commented Aug 3, 2020

My local library has now been updated with tested code, this will be released in the next 2 weeks. In the meantime the 16 bit code should provide the solution you need.

@Bodmer
Copy link
Owner

Bodmer commented Aug 5, 2020

Library updated, please raise any bugs in new issue. Thanks.

@dede53
Copy link
Author

dede53 commented Aug 8, 2020

Now it works perfectly!

Thank you very much!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants