Open drp0 opened 1 year ago
I am using a 320x240 screen, with utft:
UTFT myGLCD(ITDB32S_V2,38,39,40,41); // latest Henning UTFT needed
// myGLCD(model, SDA, SCL, CS, RST [, RS] )
#define TFT_CS 40
#define SD_CS 53
The issue appears to revolve around renderJPEG and the alignment of the bit array pImg. I can reduce the lanscape error by rotating each pImg array clockwise by 90 degrees before pushing onto the lcd:
Test Image (320x240 px):
Portrait mode left:
Portrait mode right:
Landscape no rotation:
Landscape with rotated bitmaps:
Some improvement shown so possibly on the right path.
My code: Includes image centering, for x=-1 or y =-1.
void jpegDraw(const char* filename, int x, int y) {
byte wstate;
// Try to open requested file on SD card
File jpegFile = SD.open(filename, O_READ);
if (!jpegFile) {
Serial.println("Jpeg file not found on SD card.");
return;
}
jpegFile.close();
Serial.print(F("Decoding image '"));
Serial.print(filename);
Serial.println('\'');
// initialise the decoder, check compatibility and gain access to image information
boolean decoded = JpegDec.decodeSdFile(filename);
if (decoded) {
// print information about the image to the serial port
jpegInfo();
int X = JpegDec.width;
int Y = JpegDec.height;
int XX = myGLCD.getDisplayXSize();
int YY = myGLCD.getDisplayYSize();
bool sx = X < XX;
bool bx = X > XX;
bool sy = Y < YY;
bool by = Y > YY;
if (filename != "arduino.jpg") {
// if (JpegDec.width > JpegDec.height) wstate = LANDSCAPE; else wstate = PORTRAIT;
// if (page != wstate) {
// myGLCD.InitLCD(wstate);
// page = wstate;
// }
myGLCD.clrScr(); // myGLCD.fillScr(0, 0, 0);
}
// render the image onto the screen at given coordinates
if (y == -1) { // centre image -x ok, -y ok
if ( sy || by ) y = int( (YY - Y) / 2); else y = 0; // if smaller than screen centre, else if bigger use -x, -y to centre
}
if (x == -1) {
if ( sx || bx ) x = int( (XX - X) / 2); else x = 0;
}
Serial.println("X, Y " + String(x) +", " + String(y));
renderJPEG(x, y, filename);
}else {
Serial.println(F("Jpeg file format not supported."));
myGLCD.clrScr(); // myGLCD.fillScr(0, 0, 0);
}
}
//====================================================================================
// Decode and render onto the TFT screen
//====================================================================================
void renderJPEG(int x, int y, const char* fname) {
// retrieve infomration about the image
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint16_t sx = JpegDec.width;
uint16_t sy = JpegDec.height;
uint16_t W = myGLCD.getDisplayXSize();
uint16_t H = myGLCD.getDisplayYSize();
uint16_t xLim = W - mcu_w;
uint16_t yLim = H - mcu_h;
uint16_t mcu_pixels = mcu_w * mcu_h;
int result = 2 * mcu_pixels;
uint16_t pixels;
int mcu_x, mcu_y;
bool started = false;
uint8_t col_h;
uint8_t col_l;
uint32_t drawTime = millis();
uint8_t buff[result];
int i;
Serial.println("\nMCUX " + String(JpegDec.MCUx) +"," + String(JpegDec.MCUy));
// read each MCU block until there are no more
while ( JpegDec.read()) {
pImg = JpegDec.pImage; // save a pointer to the image block
// uint16_t *pStart = pImg;
// for (i = 0; i < result; i += 2) { // pImg to byte buff
// buff[i] = (*pStart) >> 8; // high byte
// buff[i + 1] = (*pStart) & 0xFF; // low byte
// pStart++;
// }
mcu_x = JpegDec.MCUx * mcu_w + x; // position using block sent
mcu_y = JpegDec.MCUy * mcu_h + y;
//Serial.println(String(mcu_x ) + "," + String(mcu_y ));
if (page == PORTRAIT) {
// test if points are on screen
if ( (mcu_x >-1) && (mcu_x <= xLim) && (mcu_y > -1) && (mcu_y <= yLim) ) {
started = true;
digitalWrite(TFT_CS, LOW); // allow tft write
myGLCD.setXY(mcu_x, mcu_y, mcu_x + mcu_w - 1, mcu_y + mcu_h - 1);
//for ( i = 0; i < result; i += 2) myGLCD.LCD_Write_DATA(buff[i], buff[i + 1]);
pixels = mcu_pixels;
while (pixels--) {
// Push each pixel to the TFT MCU area
col_h = (*pImg) >> 8; // High byte
col_l = (*pImg) & 0xFF; // Low byte
pImg++; // Increment pointer
myGLCD.LCD_Write_DATA(col_h, col_l); // Send a pixel colour to window
}
digitalWrite(TFT_CS, HIGH);
} // end on screen test
}else{ // LANDSCAPE
if ( (mcu_x >-1) && (mcu_x <= xLim) && (mcu_y > -1) && (mcu_y <= yLim) ) {
started = true;
digitalWrite(TFT_CS, LOW); // allow tft write
myGLCD.setXY(mcu_x, mcu_y, mcu_x + mcu_w - 1, mcu_y + mcu_h - 1);
// pixels = mcu_pixels;
// while (pixels--) {
// // Push each pixel to the TFT MCU area
// col_h = (*pImg) >> 8; // High byte
// col_l = (*pImg) & 0xFF; // Low byte
// pImg++; // Increment pointer
// myGLCD.LCD_Write_DATA(col_h, col_l); // Send a pixel colour to window
// }
int n[mcu_pixels];
for (i = 0; i < mcu_pixels; i ++) { // pImg to byte buff
n[i] = (*pImg);
pImg++;
}
matrix(n, mcu_pixels, false); // rotate array true:left false:right
for ( i = 0; i < mcu_pixels; i ++) {
col_h = n[i] >> 8; // High byte
col_l = n[i] & 0xFF; // Low byte
myGLCD.LCD_Write_DATA(col_h, col_l);
}
digitalWrite(TFT_CS, HIGH);
}
} // end else if (state == portrait)
if (started) {
if ( mcu_y > yLim ) break;
}
} // end while ( JpegDec.read())
JpegDec.abort();
drawTime = millis() - drawTime; // calculate how long it took to draw the image
myGLCD.print(fname, CENTER, Ly);
Serial.print(F( "Total render time was : ")); Serial.print(drawTime); Serial.println(F(" ms\n"));
}
My array rotation code:
void matrix(int in[], int v, bool left) {
int size = sqrt(v);
int p;
int r = 0;
int c = 0;
int m[size][size];
for (p = 0; p < v; p++) { // 1d to 2d
m[r][c] = in[p];
c++;
if (c == size) {
c = 0;
r++;
}
}
for (r = 0; r < size/2; r++) { // rotate
for (c = r; c < (size - r - 1); c++) {
int tmp = m[c][r];
m[c][r] = m[size -r -1][c];
m[size - r - 1][c] = m[size - c -1][size - r - 1];
m[size - c - 1][size - r - 1] = m[r][size - c -1];
m[r][size - c - 1] = tmp;
}
}
if (left){ // 2d to 1d
p = 15;
for (r = 0; r < size; r++) {
for (c = 0; c < size; c++) {
in[p] = m[r][c];
p--;
}
}
}else{
p = 0;
for (r = 0; r < size; r++) {
for (c = 0; c < size; c++) {
in[p] = m[r][c];
p++;
}
}
}
}
Rotating the bit image anticlockwise does not improve the result.
I am out of ideas, now.
Any suggestions? David
The problem is that UTFT does not use the hardware rotation features built into the display. This is possibly because it supports some displays that do not have this feature. This ultimately means that the display is kept in portrait orientation but x and y for each pixel are swappped (if required) and coordinates adjusted (if required) by subtracting from the width and height of display.
There are a few ways to deal with this:
Personally I would avoid the UTFT library and use TFT_eSPI or Adafuit_GFX. Those libraries make thing simpler and use hardware rotation and have a performance advantage. Performing the rotations in software for every pixel has a performance impact, but does make the library "universal" which is as the author intended.
Thanks for the reply. I have around 6 tft units, none of which appear to be compatible with the TFT_espi or adafruit GFX libraries.
Does landscape work using the library example with any hardware combinations that you have tested?
I am using a mega with a ITDB32S_V2 screen.
Re 1 & 2, As detailed above I have extracted the MCU block and rotated it, with some improvement with simple lined shapes. With detailed pictures, the screen image is barely recognisable.
Re 3 & 4, I had already tried the pre-rotating trick but this would not be a satisfactory solution.
Is JpegDec.MCUx properly tied into the screen alignment, or is it specifically returning portrait positions? At what point is JpegDec.MCUx updated? Does the JpegDec.read() function read in a linear book-reading fashion through the jpeg (in x by y blocks) ?
David
Intuitively, looking at the image where blocks are not rotated, a 90 degree anticlockwise rotation of each MCU block will correct the image. What does it look like?
90 degree anticlockwise was a good call. I also had to rewrite the anticlockwise rotation section using google code. I have centring and positioning working, with options to auto-rotate the screen for portrait / landscape pictures. The matrix rotation has a time overhead of less than 1s on a 320x240 image. Bearing in mind that portrait mode crops the pushed area, it is probably less than this. Here is the full code. You are welcome to use it as you see fit.
David
main:
/*
UTFT_SD_Jpeg4.ino
Modified jpeg decoder
Supports PORTRAIT and LANDSCAPE
D.R.Patterson
3/5/2023
This sketch draws Jpeg files stored on an SD card on a TFT screen, it is based on the UTFT_Bitmap
by Henning Karlsen.
web: http://www.RinkyDinkElectronics.com/
The demo should also run on an Arduino Mega, but it will be slower
By default the UTFT library does not configure the Gamma curve settings for the ILI9341 TFT,
so photo images may not render well. To correct this ensure the set Gamma curve section
in initlcd.h (library folder UTFT\tft_drivers\ili9341\s5p\initlcd.h) is NOT commented out.
You can generate your own Jpeg images from digital photographs by cropping and resizing
by using commonly available picture/image editors such as Paint or IrfanView.
The latest JPEGDecoder library can be found here:
https://github.com/Bodmer/JPEGDecoder
Information on JPEG compression can be found here:
https://en.wikipedia.org/wiki/JPEG
====================================================================================
libraries
====================================================================================
*/
#include <SPI.h>
#include <SD.h> // Use the Arduino IDE built-in SD library
#include <UTFT.h>
UTFT myGLCD(ITDB32S_V2,38,39,40,41); // For new screens latest Henning UTFT needed
// myGLCD(model, SDA, SCL, CS, RST [, RS] )
#define TFT_CS 40 // Chip Select for TFT
extern uint8_t SmallFont[]; // Declare which fonts we will be using
#include <JPEGDecoder.h> // JPEG decoder library
// SD card connects to hardware SPI pins MOSI, MISO and SCK and the following chip select
#define SD_CS 53 // Chip Select for SD card
#define defaultselect 53 // Mega default select
char * files[] = {"cross.jpg", "david.jpg", "tiger.jpg", "/xsail/mast2.jpg", "Baboon20.jpg", "Baboon40.jpg", "EagleEye.jpg", "lena20k.jpg", "Mouse480.jpg" };
unsigned int Ly;
unsigned int N = sizeof(files)/sizeof(*files); // Number of filenames
// ===================================================================================
// Options
// ===================================================================================
byte page = LANDSCAPE; // PORTRAIT / LANDSCAPE
const bool autoOrient = true; // if true align screen to picture orientation
const bool doOver = false; // add arduino logo
int posx = -1; // x position, -1 for centred
int posy = -1; // y position, -1 for centred
byte index = 0; // index of 1st file to be displayed
//====================================================================================
// setup
//====================================================================================
void setup() {
Serial.begin(115200);
delay(500);
pinMode(SD_CS, OUTPUT);
if (SD_CS != defaultselect) pinMode(defaultselect, OUTPUT);
// Initialise the SD card interface, check it is OK
Serial.print(F("Initialising SD card..."));
if (!SD.begin(SD_CS)){
Serial.println(F("failed!"));
return;
}else Serial.println(F("SD ok"));
myGLCD.InitLCD(page);
myGLCD.clrScr();
myGLCD.setFont(SmallFont);
myGLCD.setColor(VGA_YELLOW);
Ly = myGLCD.getDisplayYSize() - 15;
//mtest(); while(true) {;} // 90 degree a-clock matrix rotation test
Serial.println(F("Ready"));
}
void loop() {
jpegDraw(files[index], posx, posy); // -1, -1: centred or 0, 0 or x, y
if (doOver) {
delay(1000);
// draw Arduino logo at a random position within the screen area
jpegDraw( "arduino.jpg", random(myGLCD.getDisplayXSize() - 160), random(myGLCD.getDisplayYSize() - 128) );
}
index += 1;
if (index == N) index = 0;
delay(4000);
}
Jpeg_utilities.ino:
/*====================================================================================
This sketch contains support functions to render the Jpeg images.
Created by Bodmer 15th Jan 2017
==================================================================================*/
//====================================================================================
// Open a Jpeg image file and displays it at the given coordinates.
//====================================================================================
void jpegDraw(const char* filename, int x, int y) {
byte wstate;
// Try to open requested file on SD card
File jpegFile = SD.open(filename, O_READ);
if (!jpegFile) {
Serial.println("Jpeg file not found on SD card.");
return;
}
jpegFile.close();
Serial.print(F("Decoding image '"));
Serial.print(filename);
Serial.println('\'');
// initialise the decoder, check compatibility and gain access to image information
boolean decoded = JpegDec.decodeSdFile(filename);
if (decoded) {
// print information about the image to the serial port
jpegInfo();
int X = JpegDec.width;
int Y = JpegDec.height;
if (filename != "arduino.jpg") {
if (autoOrient) {
if (JpegDec.width > JpegDec.height) wstate = LANDSCAPE; else wstate = PORTRAIT;
if (page != wstate) {
myGLCD.InitLCD(wstate);
page = wstate;
myGLCD.setFont(SmallFont);
myGLCD.setColor(VGA_YELLOW);
Ly = myGLCD.getDisplayYSize() - 15;
}
}
myGLCD.clrScr(); // myGLCD.fillScr(0, 0, 0);
}
int XX = myGLCD.getDisplayXSize();
int YY = myGLCD.getDisplayYSize();
bool sx = X < XX;
bool bx = X > XX;
bool sy = Y < YY;
bool by = Y > YY;
// render the image onto the screen at given coordinates
if (y == -1) { // centre image -x ok, -y ok
if ( sy || by ) y = int( (YY - Y) / 2); else y = 0; // if smaller than screen centre, else if bigger use -x, -y to centre
}
if (x == -1) {
if ( sx || bx ) x = int( (XX - X) / 2); else x = 0;
}
Serial.println("X, Y " + String(x) +", " + String(y));
renderJPEG(x, y, filename);
}else {
Serial.println(F("Jpeg file format not supported."));
myGLCD.clrScr(); // myGLCD.fillScr(0, 0, 0);
}
}
//====================================================================================
// Decode and render onto the TFT screen
//====================================================================================
void renderJPEG(int x, int y, const char* fname) {
// retrieve infomration about the image
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint16_t sx = JpegDec.width;
uint16_t sy = JpegDec.height;
uint16_t W = myGLCD.getDisplayXSize();
uint16_t H = myGLCD.getDisplayYSize();
uint16_t xLim = W - mcu_w;
uint16_t yLim = H - mcu_h;
uint16_t yQuit = H - 2;
uint16_t mcu_pixels = mcu_w * mcu_h;
uint16_t pixels;
int16_t mcu_x, mcu_y;
bool started = false;
uint8_t col_h;
uint8_t col_l;
uint16_t incx, incy, lastY;
int16_t residue;
int i;
uint32_t drawTime = millis();
digitalWrite(TFT_CS, LOW); // allow tft write
// read each MCU block until there are no more
while ( JpegDec.read()) {
pImg = JpegDec.pImage; // save a pointer to the image block
mcu_x = JpegDec.MCUx * mcu_w + x; // position using block sent
mcu_y = JpegDec.MCUy * mcu_h + y; // Serial.println(String(mcu_x ) + "," + String(mcu_y ));
incx = mcu_w;
if (mcu_x > xLim) { // test if approaching screen width and reduce push window
residue = W - mcu_x;
if (residue > 0) incx = residue;
}
incy = mcu_h;
if (mcu_y > yLim) { // test if approaching screen height and reduce push window
residue = H - mcu_y;
if (residue > 0) incy = residue;
}
// test if points are on screen
if ( (mcu_x >-1) && (mcu_x < W) && (mcu_y > -1) && (mcu_y < H) ) {
started = true;
lastY = mcu_y + incy - 1;
myGLCD.setXY(mcu_x, mcu_y, mcu_x + incx - 1, lastY);
if (page == PORTRAIT) {
pixels = mcu_pixels;
while (pixels--) { // Push each pixel to the TFT MCU area
col_h = (*pImg) >> 8; // High byte
col_l = (*pImg) & 0xFF; // Low byte
myGLCD.LCD_Write_DATA(col_h, col_l); // Send a pixel colour to window
pImg++; // Increment pointer
}
}else{ // LANDSCAPE
int n[mcu_pixels]; // push pixels from array
for (i = 0; i < mcu_pixels; i ++) { // pImg to byte buff
n[i] = (*pImg);
pImg++;
}
Rmatrix(n, mcu_pixels); // rotate array n 90 degrees anti clockwise
for (i = 0; i < mcu_pixels; i ++) {
col_h = n[i] >> 8; // High byte
col_l = n[i] & 0xFF; // Low byte
myGLCD.LCD_Write_DATA(col_h, col_l);
}
} // end else if (state == portrait)
} // end on screen test
if (started) {
if ( mcu_y > yQuit ) break;
}
} // end while ( JpegDec.read())
JpegDec.abort();
myGLCD.setXY(0, 0, W-1, H-1); digitalWrite(TFT_CS, HIGH);
drawTime = millis() - drawTime; // calculate how long it took to draw the image
myGLCD.print(fname, CENTER, Ly);
Serial.print(F( "Total render time was : ")); Serial.print(drawTime); Serial.println(F(" ms\n"));
}
//====================================================================================
// Open a Jpeg file and send it to the Serial port in a C array compatible format
//====================================================================================
void createArray(const char *filename) {
// Open the named file
File jpgFile = SD.open( filename, O_READ); // get file handle reference for SD library
if ( !jpgFile ) {
Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
return;
}
uint8_t data;
byte line_len = 0;
Serial.println("");
Serial.println("// Generated by a JPEGDecoder library example sketch:");
Serial.println("// https://github.com/Bodmer/JPEGDecoder\n");
Serial.println("#if defined(__AVR__)");
Serial.println(" #include <avr/pgmspace.h>");
Serial.println("#endif");
Serial.println("");
Serial.print ("const uint8_t ");
while (*filename != '.') Serial.print(*filename++);
Serial.println("[] PROGMEM = {"); // PROGMEM added for AVR processors, it is ignored by Due
while ( jpgFile.available()) {
data = jpgFile.read();
Serial.print("0x"); if (abs(data) < 16) Serial.print("0");
Serial.print(data, HEX); Serial.print(","); // Add value and comma
line_len++;
if ( line_len >= 32) {
line_len = 0;
Serial.println();
}
}
Serial.println("};\r\n");
jpgFile.close();
}
//====================================================================================
//====================================================================================
// Print information about the decoded Jpeg image
//====================================================================================
void jpegInfo() {
Serial.println(F("================="));
Serial.println(F(" JPEG image info"));
Serial.println(F("================="));
Serial.print(F(" Width :")); Serial.println(JpegDec.width);
Serial.print(F(" Height :")); Serial.println(JpegDec.height);
Serial.print(F(" Components :")); Serial.println(JpegDec.comps);
Serial.print(F(" MCU / row :")); Serial.println(JpegDec.MCUSPerRow);
Serial.print(F(" MCU / col :")); Serial.println(JpegDec.MCUSPerCol);
Serial.print(F(" Scan type :")); Serial.println(JpegDec.scanType);
Serial.print(F(" MCU width :")); Serial.println(JpegDec.MCUWidth);
Serial.print(F(" MCU height :")); Serial.println(JpegDec.MCUHeight);
Serial.println(F("================="));
}
matrix.ino:
void Rmatrix(int in[], int v) {
// https://www.geeksforgeeks.org/inplace-rotate-square-matrix-by-90-degrees/
int size = sqrt(v);
int j = 0;
int i = 0;
int p, start, end, tmp;
int m[size][size]; // local 2d array
for (p = 0; p < v; p++) { // 1d to 2d
m[j][i] = in[p];
i++;
if (i == size) {
i = 0;
j++;
}
}
for (i = 0; i < size; i++) {
start = 0; // Initialise start and end index
end = size - 1;
// Till start < end, swap the element at start and end index
while (start < end) {
// Swap the element
tmp = m[i][start];
m[i][start] = m[i][end];
m[i][end] = tmp;
start++; // Increment start and
end--; // decrement end for next pair of swapping
}
}
for (int i = 0; i < size; i++) { // Perform Transpose
for (int j = i; j < size; j++) {
tmp = m[i][j];
m[i][j] = m[j][i];
m[j][i] = tmp;
}
}
p = 0; // 2d to 1d for left rotation
for (j = 0; j < size; j++) {
for (i = 0; i < size; i++) {
in[p] = m[j][i];
p++;
}
}
}
void mtest(){
// matrix rotation test
int q[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
Rmatrix(q, 16);
for (int p = 0; p < 16; p++) {
Serial.print("\t"); Serial.print(q[p]);
}
Serial.println();
}
The example works perfectly for all of the supplied images in PORTRAIT. If myGLCD.InitLCD(LANDSCAPE); is used the image drawn is not correct. Is this a problem with renderJPEG ? Can a landscape alternative be provided?
David