Merge pull request #641 from kamorris/kamorris-patch-1

Add 4bit images to sprites, with an example and a tool to produce the images from bmp files.
This commit is contained in:
Bodmer
2020-06-19 10:05:01 +01:00
committed by GitHub
8 changed files with 1917 additions and 7 deletions

View File

@@ -872,12 +872,46 @@ void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_
}
else if (_bpp == 4)
{
// not supported. The image is unlikely to have the correct colors for the color map.
// we could implement a way to push a 4-bit image using the color map?
#ifdef TFT_eSPI_DEBUG
Serial.println("pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data) not implemented");
#endif
return;
// the image is assumed to be 4 bit, where each byte corresponds to two pixels.
// much faster when aligned to a byte boundary, because the alternative is slower, requiring
// tedious bit operations.
const uint8_t *dataBuf = (uint8_t *)data;
int sWidth = (_iwidth >> 1);
if ((xs & 0x01) == 0 && (xo & 0x01) == 0 && (ws & 0x01) == 0)
{
if ((ws & 0x01) == 0) // use memcpy for better perf.
{
xs = (xs >> 1) + ys * sWidth;
ws = (ws >> 1);
xo = (xo >> 1) + yo * (w>>1);
while (hs--)
{
memcpy(_img4 + xs, dataBuf + xo, ws);
xo += (w >> 1);
xs += sWidth;
}
}
}
else // not optimized
{
for (int32_t yp = yo; yp < yo + hs; yp++)
{
x = xs;
for (int32_t xp = xo; xp < xo + ws; xp++)
{
uint32_t color;
if ((xp & 0x01) == 0)
color = (dataBuf[((xp+yp*w)>>1)] & 0xF0) >> 4; // even index = bits 7 .. 4
else
color = dataBuf[((xp-1+yp*w)>>1)] & 0x0F; // odd index = bits 3 .. 0.
drawPixel(x, ys, color);
x++;
}
ys++;
}
}
}
else // 1bpp

View File

@@ -115,7 +115,7 @@ class TFT_eSprite : public TFT_eSPI {
// 16bpp = colour, 8bpp = byte, 4bpp = colour index, 1bpp = 1 or 0
uint16_t readPixelValue(int32_t x, int32_t y);
// Write an image (colour bitmap) to the sprite. Not implemented for _bpp == 4.
// Write an image (colour bitmap) to the sprite.
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, uint16_t *data);
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, const uint16_t *data);

26
Tools/Images/README.md Normal file
View File

@@ -0,0 +1,26 @@
## bmp2array4bit
bmp2array4bit.py reads a bmp file, and creates C (or C++) code that contains two arrays for adding images to four-bit sprites. See [Sprite_image_4bit](../../examples/Sprite/Sprite_image_4bit) for an example.
It is loosely based on Spark Fun's bmp2array script, https://github.com/sparkfun/BMPtoArray/blob/master/bmp2array.py. The bmp file format is documented in https://en.wikipedia.org/wiki/BMP_file_format.
You'll need python 3.6 (the original uses Python 2.7)
`usage: python bmp2array4bit.py [-v] star.bmp [-o myfile.c]`
Create the bmp file in Gimp (www.gimp.org) from any image as follows:
* Remove the alpha channel (if it has one)
Layer -> Transparency -> Remove Alpha Channel
* Set the mode to indexed.
Image -> Mode -> Indexed...
* Select Generate optimum palette with 16 colors (max)
* Export the file with a .bmp extension. Do **NOT** select options:
* Run-Length Encoded
* Compatibility Options: "Do not write color space information"
* There are no Advanced Options available with these settings
(There are other tools that will produce bmp files, and these should work provided you don't use run-length encoding or other advanced features).
The first array produced is the palette for the image.
The second is the image itself.

View File

@@ -0,0 +1,251 @@
'''
This script takes in a bitmap and outputs a text file that is a
byte array used in Arduino files.
It is loosely based on Spark Fun's bmp2array script.
You'll need python 3.6 (the original use Python 2.7)
usage: python fourbitbmp2array.py [-v] star.bmp [-o myfile.c]
Create the bmp file in Gimp by :
. Remove the alpha channel (if it has one) Layer -> Transparency -> Remove Alpha Channel
. Set the mode to indexed. Image -> Mode -> Indexed...
. Select Generate optimum palette with 16 colors (max)
. Export the file with a .bmp extension. Options are:
. Run-Length Encoded: not selected
. Compatibility Options: "Do not write color space information" not selected
. There are no Advanced Options available with these settings
'''
import sys
import struct
import math
import argparse
import os
debug = None
def debugOut(s):
if debug:
print(s)
# look at arguments
parser = argparse.ArgumentParser(description="Convert bmp file to C array")
parser.add_argument("-v", "--verbose", help="debug output", action="store_true")
parser.add_argument("input", help="input file name")
parser.add_argument("-o", "--output", help="output file name")
args = parser.parse_args()
if not os.path.exists(args.input):
parser.print_help()
print("The input file {} does not exist".format(args.input))
sys.exit(1)
if args.output == None:
output = os.path.basename(args.input).replace(".bmp", ".c")
else:
output = args.output
debug = args.verbose
try:
#Open our input file which is defined by the first commandline argument
#then dump it into a list of bytes
infile = open(args.input,"rb") #b is for binary
contents = bytearray(infile.read())
infile.close()
except:
print("could not read input file {}".format(args.input))
sys.exit(1)
# first two bytes should be "BM"
upto = 2
#Get the size of this image
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
fileSize = struct.unpack("I", bytearray(data))
upto += 4
# four bytes are reserved
upto += 4
debugOut("Size of file: {}".format(fileSize[0]))
#Get the header offset amount
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
offset = struct.unpack("I", bytearray(data))
debugOut("Offset: {}".format(offset[0]))
upto += 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
headersize = struct.unpack("I", bytearray(data))
headerLength = headersize[0]
startOfDefinitions = headerLength + upto
debugOut("header size: {}, up to {}, startOfDefinitions {}".format(headersize[0], upto, startOfDefinitions))
upto += 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("width: {}".format(t[0]))
width = t[0]
upto += 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("height: {}".format(t[0]))
height = t[0]
# 26
upto += 4
data = struct.pack("BB", contents[upto], contents[upto+1])
t = struct.unpack("H", bytearray(data))
debugOut("planes: {}".format(t[0]))
upto = upto + 2
data = struct.pack("BB", contents[upto], contents[upto+1])
t = struct.unpack("H", bytearray(data))
debugOut("bits per pixel: {}".format(t[0]))
bitsPerPixel = t[0]
upto = upto + 2
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("biCompression: {}".format(t[0]))
upto = upto + 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("biSizeImage: {}".format(t[0]))
upto = upto + 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("biXPelsPerMeter: {}".format(t[0]))
upto = upto + 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("biYPelsPerMeter: {}".format(t[0]))
upto = upto + 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("biClrUsed: {}".format(t[0]))
colorsUsed = t
upto = upto + 4
data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
t = struct.unpack("I", bytearray(data))
debugOut("biClrImportant: {}".format(t[0]))
upto += 4
debugOut("Upto: {} Number of colors used: {} definitions start at: {}".format(upto, colorsUsed[0], startOfDefinitions))
#Create color definition array and init the array of color values
colorIndex = [] #(colorsUsed[0])
for i in range(colorsUsed[0]):
colorIndex.append(0)
#Assign the colors to the array. upto = 54
# startOfDefinitions = upto
for i in range(colorsUsed[0]):
upto = startOfDefinitions + (i * 4)
blue = contents[upto]
green = contents[upto + 1]
red = contents[upto + 2]
# ignore the alpha channel.
# data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
# t = struct.unpack("I", bytearray(data))
# colorIndex[i] = t[0]
colorIndex[i] = (((red & 0xf8)<<8) + ((green & 0xfc)<<3)+(blue>>3))
debugOut("color at index {0} is {1:04x}, (r,g,b,a) = ({2:02x}, {3:02x}, {4:02x}, {5:02x})".format(i, colorIndex[i], red, green, blue, contents[upto+3]))
#debugOut(the color definitions
# for i in range(colorsUsed[0]):
# print hex(colorIndex[i])
# perfect, except upside down.
#Make a string to hold the output of our script
arraySize = (len(contents) - offset[0])
outputString = "/* This was generated using a script based on the SparkFun BMPtoArray python script" + '\n'
outputString += " See https://github.com/sparkfun/BMPtoArray for more info */" + '\n\n'
outputString += "static const uint16_t palette[" + str(colorsUsed[0]) + "] = {";
for i in range(colorsUsed[0]):
# print hexlify(colorIndex[i])
if i % 4 == 0:
outputString += "\n\t"
outputString += "0x{:04x}, ".format(colorIndex[i])
outputString = outputString[:-2]
outputString += "\n};\n\n"
outputString += "// width is " + str(width) + ", height is " + str(height) + "\n"
outputString += "static const uint8_t myGraphic[" + str(arraySize) + "] PROGMEM = {" + '\n'
if bitsPerPixel != 4:
print("Expected 4 bits per pixel; found {}".format(bitsPerPixel))
sys.exit(1)
#Start converting spots to values
#Start at the offset and go to the end of the file
dropLastNumber = True #(width % 4) == 2 or (width % 4) == 1
paddedWidth = int(math.ceil(bitsPerPixel * width / 32.0) * 4)
debugOut("array range is {} {} len(contents) is {} paddedWidth is {} width is {}".format(offset[0], fileSize[0], len(contents), paddedWidth, width))
r = 0
width = int(width / 2)
#for i in range(offset[0], fileSize[0]): # close but image is upside down. Each row is correct but need to swap columns.
#for i in range(fileSize[0], offset[0], -1):
for col in range(height-1, -1, -1):
i = 0
for row in range(width):
colorCode1 = contents[row + col*paddedWidth + offset[0]]
if r > 0 and r % width == 0:
i = 0
outputString += '\n\n'
elif (i + 1) % 12 == 0 :
outputString += '\n'
i = 0
#debugOut("cell ({0}, {1})".format(row, col)
r = r + 1
i = i + 1
outputString += "0x{:02x}, ".format(colorCode1)
#Once we've reached the end of our input string, pull the last two
#characters off (the last comma and space) since we don't need
#them. Top it off with a closing bracket and a semicolon.
outputString = outputString[:-2]
outputString += "};"
try:
#Write the output string to our output file
outfile = open(output, "w")
outfile.write(outputString)
outfile.close()
except:
print("could not write output to file {}".format(output))
sys.exit(1)
debugOut("{} complete".format(output))
debugOut("Copy and paste this array into a image.h or other header file")
if not debug:
print("Completed; the output is in {}".format(output))

BIN
Tools/Images/star.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,152 @@
/*
Sketch to show how a Sprite can use a four-bit image with
a palette to change the appearance of an image while rendering
it only once.
Example for library:
https://github.com/Bodmer/TFT_eSPI
A Sprite is notionally an invisible graphics screen that is
kept in the processors RAM. Graphics can be drawn into the
Sprite just as it can be drawn directly to the screen. Once
the Sprite is completed it can be plotted onto the screen in
any position. If there is sufficient RAM then the Sprite can
be the same size as the screen and used as a frame buffer.
A 16 bit Sprite occupies (2 * width * height) bytes in RAM.
On a ESP8266 Sprite sizes up to 126 x 160 can be accomodated,
this size requires 40kBytes of RAM for a 16 bit color depth.
When 8 bit color depth sprites are created they occupy
(width * height) bytes in RAM, so larger sprites can be
created, or the RAM required is halved.
*/
// Set delay after plotting the sprite
#define DELAY 30
// Width and height of sprite
#define WIDTH 164
#define HEIGHT 164
#include "sample_images.h"
TFT_eSPI tft = TFT_eSPI(); // Declare object "tft"
TFT_eSprite spr = TFT_eSprite(&tft); // Declare Sprite object "spr" with pointer to "tft" object
byte red = 31; // Red is the top 5 bits of a 16 bit colour value
byte green = 0;// Green is the middle 6 bits
byte blue = 0; // Blue is the bottom 5 bits
byte state = 0;
int rloop = 0;
int incr = 1;
uint16_t cmap[16];
void setup()
{
Serial.begin(9600);
Serial.println();
delay(50);
// Initialise the TFT registers
tft.init();
spr.setColorDepth(4);
// Create a sprite of defined size
spr.createSprite(WIDTH, HEIGHT);
// Clear the TFT screen to black
tft.fillScreen(TFT_BLACK);
// push the image - only need to do this once.
spr.pushImage(2, 2, 160, 160, (uint16_t *)stars);
for (int i = 0; i < 16; i++)
cmap[i] = rainbow();
}
void loop(void)
{
// create a palette with the defined colors and push it.
spr.createPalette(cmap, 16);
spr.pushSprite(tft.width() / 2 - WIDTH / 2, tft.height() / 2 - HEIGHT / 2);
// update the colors
for (int i = 0; i < 15; i++) {
cmap[i] = cmap[i + 1];
}
if (incr == 2) {
(void)rainbow(); // skip alternate steps to go faster
}
cmap[15] = rainbow();
rloop += incr;
if (rloop > 0xc0) {
incr = incr == 2 ? 1 : 2;
rloop = 0;
}
delay(DELAY);
}
// #########################################################################
// Return a 16 bit rainbow colour
// #########################################################################
unsigned int rainbow()
{
switch (state) {
case 0:
green ++;
if (green == 64) {
green = 63;
state = 1;
}
break;
case 1:
red--;
if (red == 255) {
red = 0;
state = 2;
}
break;
case 2:
blue ++;
if (blue == 32) {
blue = 31;
state = 3;
}
break;
case 3:
green --;
if (green == 255) {
green = 0;
state = 4;
}
break;
case 4:
red ++;
if (red == 32) {
red = 31;
state = 5;
}
break;
case 5:
blue --;
if (blue == 255) {
blue = 0;
state = 0;
}
break;
}
return red << 11 | green << 5 | blue;
}

View File

@@ -0,0 +1,3 @@
#include <TFT_eSPI.h> // Include the graphics library (this includes the sprite functions)
extern const uint8_t stars[12800] PROGMEM ;

File diff suppressed because it is too large Load Diff