0

Adafruit_ImageReader transparent background
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Adafruit_ImageReader transparent background

by darkenedforest on Fri Aug 27, 2021 11:28 pm

Is there a way to load a bitmap from the SD card and set a transparent color? I browsed through the Adafruit_ImageReader library but it does not look to be an option in there.

There is a tutorial for making a tile game using CircuitPython here https://learn.adafruit.com/creating-your-first-tilemap-game-with-circuitpython/circuitpython-setup that has this bit of code:

Code: Select all | TOGGLE FULL SIZE
# Load the sprite sheet (bitmap)
sprite_sheet, palette = adafruit_imageload.load(
    "tilegame_assets/sprite_sheet.bmp",
    bitmap=displayio.Bitmap,
    palette=displayio.Palette,
)

# make green be transparent so entities can be drawn on top of map tiles
palette.make_transparent(0)


Is there no equivalent for C++?

darkenedforest
 
Posts: 15
Joined: Wed Jul 07, 2021 11:04 pm

Re: Adafruit_ImageReader transparent background

by mikeysklar on Sat Aug 28, 2021 1:48 pm

@darkendforest,

Interesting discussion on achieving transparent background.

https://forum.pjrc.com/archive/index.php/t-27917.html

The SpiTFTbitmap library might be a good starting point. At least the thread above suggests this is the closest to having transparency support (in C).

https://github.com/PaulStoffregen/ILI93 ... bitmap.ino

mikeysklar
 
Posts: 5813
Joined: Mon Aug 01, 2016 8:10 pm

Re: Adafruit_ImageReader transparent background

by darkenedforest on Sat Aug 28, 2021 7:46 pm

Yeah, I've read through that post before. I'm a bit surprised that someone hasn't written a library for this purpose already. As near as I can tell, the process would be as follows:

Makea BMP sprite in a some drawing software and pick a color for transparent pixels
Load the bg image into memory
Load the sprite into memory
Use the sprite's origin point to find that location in the bg image memory array
Step through the sprite memory array and bg image memory array together
If the color of the sprite pixel is the color declared as transparent, replace the value in the sprite array with the value in the bg image array
Once done, draw the sprite. Or maybe just draw each pixel as you go along.

I wonder what the speed constraints would be with this technique. Do you think iterating through both those locations in memory for each frame would create a noticeable delay in animation?
Does anyone know of a better method?

Also, would someone be able to show how to read the bytes in an Adafruit_Image? I have been able to print the value in each index of a small hard coded byte array to the serial monitor, but when I try to look at an Adafruit_Image I get a type error.

Something like this would be awesome:
Code: Select all | TOGGLE FULL SIZE
#include <Adafruit_ImageReader.h>
#include <SdFat.h>

SdFat  SD;         
Adafruit_ImageReader reader(SD);
Adafruit_Image image;
int len;

void setup() {
  Serial.begin(9600);
  reader.loadBMP("/sprite.bmp", image);

  len = some magical way to get the length of image

  for (int i = 0; i < len; i++) {
    Serial.println(some fancy trickery to print the value of each index in image)
  }
 
}

void loop() {

}

darkenedforest
 
Posts: 15
Joined: Wed Jul 07, 2021 11:04 pm

Re: Adafruit_ImageReader transparent background

by mikeysklar on Mon Aug 30, 2021 5:20 pm

The current generation of microcontrollers RP2040 / M4 / NRF52 are so fast and loaded with SRAM that it should remove the concern for speed constraints for the animation transparency method you have suggested. I would make sure you are using libraries that support DMA like Adafruit_GFX which works well with Adafruit_ZeroDMA.

Adafruit_ImageReader has a readLE16() and readLE32() you might also find coreBMP() in that library to be useful.

https://adafruit.github.io/Adafruit_Ima ... eader.html

mikeysklar
 
Posts: 5813
Joined: Mon Aug 01, 2016 8:10 pm

Re: Adafruit_ImageReader transparent background

by darkenedforest on Mon Aug 30, 2021 7:05 pm

Thanks Mike,

I poked around in in the Image reader library. I saw that in coreBMP the image file gets loaded from the SD card then it goes through pixel by pixel and converts it to 565 format and loads it into memory. Since coreBMP is protected and so the function and variable that stores the pixel array are not accessable, I decided to make a new derived class of ImageReader where I could stick a copy of coreBMP and repurpose it for my needs. Currently stuck on changing the return value. But I'll get it.

I also figured, rather than a find/replace of the pixels with the sprite and bg images, I could probably use the drawPixel function from the GFX library. Then I can just get the image width and keep track of x and y. I think I could just increment through the pixel array. If the value is not the special color then draw the pixel and increment x. If x is greater than the image width, increment y and set x to it's origin value. If it is the special color, then just increment x or y.

Then, I can find the starting x,y of the sprite within the bg array and get the bytes equal to the length of the sprite array and pass that into drawBitmap to cover up the sprite image for the next frame.

As I type this, I'm realizing I might have to deal with some clipping issues if the image moves off the screen. But I suppose I'll cross that bridge when I come to it.

As a personal aside. I believe the Genova convention outta ban c++ programming as a form of torture, if they haven't already, as I feel strongly that it is too inhumane.

darkenedforest
 
Posts: 15
Joined: Wed Jul 07, 2021 11:04 pm

Re: Adafruit_ImageReader transparent background

by mikeysklar on Tue Aug 31, 2021 10:40 am

Nice progress on working out a flow to get the animations you desire. I kind of agree with extending the Genova convention to include C++ as a form of torture.

mikeysklar
 
Posts: 5813
Joined: Mon Aug 01, 2016 8:10 pm

Re: Adafruit_ImageReader transparent background

by darkenedforest on Mon Sep 20, 2021 3:15 am

Well after several weeks of beating my head against the proverbial wall, I finally got somewhere. I also know A LOT more about the Adafruit Image_Reader library.

I have managed to read and write to the Adafruit_Image object buffer. So I can do a find and replace of pixels of a specified color with the pixel color of the corresponding background.

I made a 45 sec video to show the progress. https://youtu.be/lk2fqWkhBDs

Once I clean things up, I'll post some code for others trying to accomplish the same thing.

I think the find and replace is not going to be fast enough though for dynamic frame animations. So, I think I'll preprocess the frames and build an index for each "transparent" color pixel. That way, I don't have to scan the entire image each time and run and if statement at every pixel. Instead, I can just cycle through the index and get the background pixel at the given x,y offset. Hopefully, that will allow for a quick replace of basically the outside border of my sprite character. For my use case, the background will be static, and the total frames will be pretty low, so I think I can get away with loading the background and all the frames into ram.

Anyway, there is is.

darkenedforest
 
Posts: 15
Joined: Wed Jul 07, 2021 11:04 pm

Re: Adafruit_ImageReader transparent background

by mikeysklar on Mon Sep 20, 2021 1:04 pm

Nice video and cool setup with the LCD presentation inside the anime themed enclosure.

Did you want to share your code for this? Do you have a github repo going?

mikeysklar
 
Posts: 5813
Joined: Mon Aug 01, 2016 8:10 pm

Re: Adafruit_ImageReader transparent background

by darkenedforest on Mon Sep 20, 2021 1:54 pm

Thanks Mike,
Let me start by saying I have no idea how to do any of this. I learned to 3D model in Fusion360 just to make that enclosure. I know a little JavaScript from work, but by no means am I professional. So C++ is somewhat new for me. And being way less flexible that JavaScript, it's tedious.

mikeysklar wrote:Did you want to share your code for this?

I intend to post a simple sketch that shows how to access the canvas buffer and do the find and replace of a pixel color after I've had a chance to clean up the code and add comments. I got it working last night and then had to head to bed. Since I could not find examples anywhere, I think it would be nice for others to have a small example.

mikeysklar wrote:Do you have a github repo going?

I do have a repo for my project, but I haven't really gotten anywhere with the project yet, because I hit wall first thing with the "transparency" issue. The repo is set to private currently. When there is enough there to make the project noteworthy, I'll make it public and start promoting it. Haha.

The goal of the project is to create a digital pet that allows the user to draw their own frames for characters and accessories, put them on the SD card and then load a character with those frames. Characters will grow and evolve. They will need to be fed and cleaned up after, played with and trained. At evolution time, based on how well you took care of your pet, the stats will increase more or less than some standard level. I would like to add battling of NPC characters as part of the training. And I'd like to use the Bluetooth or WIFI module of the ESP32 to allow for connecting to other pets to battle friends. If I get ambitious enough, and there is community support there, I think it would be cool to have a server where you can connect to battle and buy and sell accessories. It would be neat to generate a crypto coin from training and winning battles, etc., that could be used in the shop to buy and sell items. Or maybe wager against another player in a battle. Obviously, because the code is readily accessible, you'd need a way to prevent people from just buffing up their character via code. Maybe checking the hash of the code against the official version on the server, or something. Anyway, that's all probably more of a pipe dream than a reality. At this point, given the complexity of loading a sprite, with the time delay of replacing pixels and the memory usage required to keep the images in RAM, there may not be much space for much actual game play.

darkenedforest
 
Posts: 15
Joined: Wed Jul 07, 2021 11:04 pm

Re: Adafruit_ImageReader transparent background

by darkenedforest on Tue Sep 21, 2021 1:06 am

Ok, here's the github page for the sample sketch https://github.com/darkenedforest/arduino-transparent-layer

Here's the code
Code: Select all | TOGGLE FULL SIZE
/*
 * Author: Tyler Baker
 * Created: 9/19/2021
 * Board: ESP32 (Adafruit Feather HUZZAH32 PID 3405
 * TFT Display: Adafruit 1.14" 240x135 PID 4383
 *
 * This sketch is intended to show how to edit the image buffer of an Adafruit_Image object to acheive a "transparent layer" effect for a sprite image
 * on top of a background image. The process is accomplished by scanning the pixels of the sprite image for the transparent color (defined below) and replacing
 * it with the corrosponding background image pixel. And additional buffer is created of the same size as the sprite image. This image is a cutout of the background
 * image where the sprite will be placed. This will act as a pacth to cover over the sprite during frame transitions.
 * A sample video of the process is available here: https://youtu.be/lk2fqWkhBDs
 */



#include <Adafruit_GFX.h>
#include <SdFat.h>
#include <Adafruit_SPIFlash.h>
#include <Adafruit_ImageReader.h>
#include <Adafruit_ST7789.h>



/*
 * Adjust these as needed
 */
#define SD_CS               12      // SD card select pin
#define TFT_CS              15      // TFT select pin
#define TFT_DC              27      // TFT display/command pin
#define TFT_RST             33      // Or set to -1 and connect to Arduino RESET pin
#define SPRITE_X            100     // Sprite x origin point
#define SPRITE_Y            60      // Sprite x origin point
#define TRANSPARENT_COLOR   63488   // Pixel color to replace with background. It's Red. RGB 255,0,0.
                                    // Why is it 63488 instead of 0xFF0000 or ST77XX_RED? Well, the truth is...
                                    // I'm not exactly sure. I tried both of those in my if statement below and neither
                                    // evaluated properly. If you log the value of the pixel to the serial monitor,
                                    // that is what you get. I'm probably implicitly converting it somewhere without
                                    // realizing it or something. I'll add it to the TODO list to figure out.



/*
 * Declare some stuff
 */
SdFat                   SD;
Adafruit_ImageReader    reader(SD);
Adafruit_ST7789         tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
ImageReturnCode         stat;
Adafruit_Image          bg;                     // Object to store background image
Adafruit_Image          sprite;                 // Object to store sprite image
int                     sprite_h, sprite_w;     // Sprite height and width   
uint16_t                *bg_patch;              // Pointer to background patch buffer, to be dynamically allocated later




void setup() {

  // Start the serial monitor
  Serial.begin(115200);



  // Initalize the display. I'm using the 1.14" 135x240 TFT. Set your size accordingly.
  tft.init(135, 240);



  tft.setRotation(3);



  // Check the SD card motor turns over
  if (!SD.begin(SD_CS, SD_SCK_MHZ(10))) { // Breakouts require 10 MHz limit due to longer wires
    Serial.println(F("SD begin() failed"));
    for (;;); // Fatal error, do not continue
  }



  // Load bg.bmp located on root folder of the SD card into bg image object
  stat = reader.loadBMP("/bg/bg.bmp", bg);



  /* 
   *  Make sure it successfully loaded. Trying to read the buffer of an empty image object will cause the world to come crashing down around you.
   *  No joke, there will be fires. Buildings will collapse. Loved ones will be lost.  Don't tempt fate or suffer the consequences.   
   */
  if (stat != IMAGE_SUCCESS) {
    for (;;); // Fatal error, do not continue
  };

 

  // Load sprite.bmp located on root folder of the SD card into bg image object
  stat = reader.loadBMP("/sprite.bmp", sprite);

 

  // Same ominous warning as above.
  if (stat != IMAGE_SUCCESS) {
    for (;;); // Fatal error, do not continue
  };



  /*
   * Ok, this is where the fun begins. This is probably why you are reading this code.
   * Adafruit_Image object holds a canvas obtained from the GFX library. So we get the canvas
   * and then get the canvas buffer to gain access to the raw pixel data.
   */


  // Declare canvas pointer
  GFXcanvas16* bg_canvas;



  /*
   *  getCanvas returns void and must be type converted. Read the description in Adafruit_Image library,
   *  file Adafruit_ImageReader.cpp, line 144 for more details.
   */
  bg_canvas = (GFXcanvas16*) bg.getCanvas();



  /*
   * Now that we have the canvas we can get a pointer to the buffer.
   */
  uint16_t* bg_buff = bg_canvas->getBuffer();



  // Do the same thing to get the pixel data for the sprite.
  GFXcanvas16* sprite_canvas;
  sprite_canvas   = (GFXcanvas16*) sprite.getCanvas();
  uint16_t*       sprite_buff = sprite_canvas->getBuffer();
  int             bg_h = bg_canvas->height(),          // Get the background image height from canvas
                  bg_w = bg_canvas->width(),           // Get the background image width from canvas
                  bg_x = 0, bg_y = 0;                  // Set the x and y origin point for background image
  sprite_h        = sprite_canvas->height();           // Get the sprite image height from canvas
  sprite_w        = sprite_canvas->width();            // Get the sprite image width from canvas

  /*
   * Dynamically allocate the memory for the backgound pactch image. Generally speaking, dynamic memory allocation
   * is considered a bad thing. So, while it works in this instance to determine the space needed for the image
   * on the sd card without know the actually dimensions of the image, don't go getting allocate and deallocate
   * happy. It will lead to fragmented memory and your microcontroller will become schizophrenic and possibly
   * develop multiple personality disorder.
   */
  bg_patch        = (uint16_t*) malloc (sprite_h * sprite_w);


 
 
  /*
   * This is an ugly set of loops. j represents each row of the image. The loop ends when all the rows have been processed.
   * i represents each pixel in the row. Since you are moving laterally through the image buffers, you have to multiple j times
   * the row width to get to to the correct place in memory that represents the start of the row. Then add i tom move that many
   * pixels into the row.
   * i.e. If the image is 20 pixesl heigh and 30 pixels wide:
   *    In the first loop j = 0. 0 x 30 = 0. So you are still at the first index position in the image buffer.
   *    i is 0, so 0 + 0 = 0. This is the first pixel. i loops through to 29. So you have 0 + 1 = 1 (second pixel),
   *    0 + 2 = 2...0 + 29 = 29 (last pixel in the first row.)
   *    Now j = 1. 1 * 30 = 30 Position 30 in the index is the first pixel in the second row, etc.
   *       
   *    It gets a little more complicated for the background image, because you are starting at the origin point of the sprite
   *    in the background image buffer. (SPRITE_Y * bg_w + SPRITE_X) then you multiple the row number (j) by the background width
   *    to wrap around the background image to the start of the next row. Then add i to move through the pixels in the row.
   *   
   *  Each pixel from the background image is stored in the previously created array "bg_patch" to build the patch image.
   *  Then the sprite image is checked to see if its the special color
   */



  for (int16_t j = 0; j < sprite_h; j++) {                               // For each row of pixels
    for (int16_t i = 0; i < sprite_w; i++) {                             // For each pixel in the row
      int index = j * sprite_w + i;                                      // This will increment 0, 1, 2, 3, etc...
      uint16_t bg_index = SPRITE_Y * bg_w + SPRITE_X + j * bg_w + i;     // Sprite origin point in background image (SPRITE_Y * bg_w + SPRITE_X) plus current number of
                                                                         // rows into the sprite image (j * bg_w) plus current number of pixels in the sprite image (i).
                                                                         // This will get you to the pixel data in the background image relative to the sprite image at the
                                                                         // same location on the display.
                                                                         
      bg_patch[index] = bg_buff[bg_index];                               // Add the current backbground pixel to the background patch buffer/
      if (sprite_buff[index] == TRANSPARENT_COLOR) {                     // If the current sprite pixel color is the transparent color,
        sprite_buff[index] = bg_buff[bg_index];                          // replace the sprite pixel with the pixel from the background
      }
    }
  }
 


  // Drop in the background. Use the draw function on the image object.
  bg.draw(tft, bg_x, bg_y);                                             // Comment out this line if you want to see how the sprite and pacth look without the background
}

void loop() {
  // Alternate drawing the modified sprite image and the background patch every second
  sprite.draw(tft, SPRITE_X, SPRITE_Y);                                 // Use the draw function on the image object.
  delay(1000);
  tft.drawRGBBitmap(SPRITE_X, SPRITE_Y, bg_patch, sprite_w, sprite_h);  // use GFX's drawRGBBitmap to draw the background patch pixel buffer
  delay(1000);
}

darkenedforest
 
Posts: 15
Joined: Wed Jul 07, 2021 11:04 pm

Re: Adafruit_ImageReader transparent background

by mikeysklar on Tue Sep 21, 2021 6:49 pm

Sweet. Thank you for sharing.

mikeysklar
 
Posts: 5813
Joined: Mon Aug 01, 2016 8:10 pm

Please be positive and constructive with your questions and comments.