From 4d0488a906ca2b11f9cec2099ea1979dff636eb8 Mon Sep 17 00:00:00 2001 From: Bodmer Date: Mon, 13 Mar 2017 02:31:59 +0000 Subject: [PATCH] Update screenshot client and server sketches Server can now provide a filepath/name, define saved image file type and specify filename extension method. Max number of images saved by client per session limited to 1000. A pause/run button has been added. --- Tools/Screenshot_client/ILI9341_example.png | Bin 5170 -> 0 bytes Tools/Screenshot_client/Screenshot_client.pde | 212 ++++++++++++++---- .../TFT_Screen_Capture/processing_sketch.ino | 211 +++++++++++++---- .../TFT_Screen_Capture/screenServer.ino | 88 ++++++-- 4 files changed, 403 insertions(+), 108 deletions(-) delete mode 100644 Tools/Screenshot_client/ILI9341_example.png diff --git a/Tools/Screenshot_client/ILI9341_example.png b/Tools/Screenshot_client/ILI9341_example.png deleted file mode 100644 index 25369e68396663915b3a300d61956023b1fcc4f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5170 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 diff --git a/Tools/Screenshot_client/Screenshot_client.pde b/Tools/Screenshot_client/Screenshot_client.pde index 29d5bad..537b108 100644 --- a/Tools/Screenshot_client/Screenshot_client.pde +++ b/Tools/Screenshot_client/Screenshot_client.pde @@ -20,8 +20,8 @@ // option "Show Sketch Folder" or press Ctrl+K // Created by: Bodmer 5/3/17 -// Updated by: Bodmer 10/3/17 -// Version: 0.06 +// Updated by: Bodmer 12/3/17 +// Version: 0.07 // MIT licence applies, all text above must be included in derivative works @@ -45,7 +45,9 @@ boolean save_border = true; // Save the image with a border int border = 5; // Border pixel width # boolean fade = false; // Fade out image after saving # // # -int max_images = 10; // Maximum of numbered saved images before over-writing files # +int max_images = 100; // Maximum of numbered file images before over-writing files # +// # +int max_allowed = 1000; // Maximum number of save images allowed before a restart # // # // # End of the values to change for a particular setup # // ########################################################################################### @@ -69,6 +71,13 @@ color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color // TFT image frame greyscale value (dark grey) color frameColor = 42; +color buttonStopped = color(255, 0, 0); +color buttonRunning = color(128, 204, 206); +color buttonDimmed = color(180, 0, 0); +boolean dimmed = false; +boolean running = true; +boolean mouseClick = false; + int[] rgb = new int[3]; // Buffer for the colour bytes int indexRed = 0; // Colour byte index in the array int indexGreen = 1; @@ -85,6 +94,9 @@ int beginTime = 0; int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive int lastPixelTime = 0; // Time that "image send" command was sent +int requestTime = 0; +int requestCount = 0; + int state = 0; // State machine current state int progress_bar = 0; // Console progress bar dot count @@ -93,6 +105,7 @@ float percentage = 0; // Percentage of pixels received int saved_image_count = 0; // Stats - number of images processed int bad_image_count = 0; // Stats - number of images that had lost pixels +String filename = ""; int drawLoopCount = 0; // Used for the fade out @@ -126,42 +139,65 @@ void setup() { } void draw() { + + if (mouseClick) buttonClicked(); + switch(state) { case 0: // Init varaibles, send start request - tint(0, 0, 0, 255); - println(); - flushBuffer(); - println(""); - print("Ready: "); + if (running) { + tint(0, 0, 0, 255); + flushBuffer(); + println(""); + print("Ready: "); - xpos = 0; - ypos = 0; - serialCount = 0; - progress_bar = 0; - pixel_count = 0; - percentage = 0; - drawLoopCount = frameCount; - lastPixelTime = millis() + 1000; + xpos = 0; + ypos = 0; + serialCount = 0; + progress_bar = 0; + pixel_count = 0; + percentage = 0; + drawLoopCount = frameCount; + lastPixelTime = millis() + 1000; - state = 1; + state = 1; + } else { + if (millis() > beginTime) { + beginTime = millis() + 500; + dimmed = !dimmed; + if (dimmed) drawButton(buttonDimmed); + else drawButton(buttonStopped); + } + } break; case 1: // Console message, give server some time print("requesting image "); serial.write("S"); delay(10); - beginTime = millis() + 1000; + beginTime = millis(); + requestTime = millis() + 1000; + requestCount = 1; state = 2; break; case 2: // Get size and set start time for rendering duration report - if (millis() > beginTime) { - System.err.println(" - no response!"); - state = 0; + if (millis() > requestTime) { + requestCount++; + print("*"); + serial.clear(); + serial.write("S"); + if (requestCount > 32) { + requestCount = 0; + System.err.println(" - no response!"); + state = 0; + } + requestTime = millis() + 1000; } if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel + getFilename(); flushBuffer(); // Precaution in case image header size increases in later versions + lastPixelTime = millis() + 1000; beginTime = millis(); state = 3; } @@ -169,7 +205,7 @@ void draw() { case 3: // Request pixels and render returned RGB values state = renderPixels(); // State will change when all pixels are rendered - + // Request more pixels, changing the number requested allows the average transfer rate to be controlled // The pixel transfer rate is dependant on four things: // 1. The frame rate defined in this Processing sketch in setup() @@ -183,17 +219,17 @@ void draw() { //serial.write("RRRR"); // 4 x NPIXELS more //serial.write("RR"); // 2 x NPIXELS more //serial.write("R"); // 1 x NPIXELS more + if (!running) state = 4; break; case 4: // Pixel receive time-out, flush serial buffer - println(); flushBuffer(); state = 6; break; case 5: // Save the image to the sketch folder (Ctrl+K to access) saveScreenshot(); - saved_image_count++; + saved_image_count++; println("Saved image count = " + saved_image_count); if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count); drawLoopCount = frameCount; // Reset value ready for counting in step 6 @@ -232,13 +268,16 @@ void drawWindow() textSize(20); fill(0); text("Bodmer's TFT image viewer", width/2, height-6); + + if (running) drawButton(buttonRunning); + else drawButton(buttonStopped); } void flushBuffer() { //println("Clearing serial pipe after a time-out"); int clearTime = millis() + 50; - while ( millis() < clearTime ) serial.read(); + while ( millis() < clearTime ) serial.clear(); } boolean getSize() @@ -274,6 +313,71 @@ boolean getSize() return false; } +void saveScreenshot() +{ + println(); + if (saved_image_count < max_allowed) + { + if (filename == "") filename = "tft_screen_" + (n++); + filename = filename + image_type; + println("Saving image as \"" + filename + "\""); + if (save_border) + { + PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border); + partialSave.save(filename); + } else { + PImage partialSave = get(x_offset, y_offset, tft_width, tft_height); + partialSave.save(filename); + } + + if (n>=max_images) n = 0; + } + else + { + System.err.println(max_allowed + " saved image count exceeded, restart the sketch"); + } +} + +void getFilename() +{ + int readTime = millis() + 20; + int inByte = 0; + filename = ""; + while ( serial.available() > 0 && millis() < readTime && inByte != '.') + { + inByte = serial.read(); + if (inByte == ' ') inByte = '_'; + if ( unicodeCheck(inByte) ) filename += (char)inByte; + } + + inByte = serial.read(); + if (inByte == '@') filename += "_" + timeCode(); + else if (inByte == '#') filename += "_" + saved_image_count%100; + else if (inByte == '%') filename += "_" + millis(); + else if (inByte != '*') filename = ""; + + inByte = serial.read(); + if (inByte == 'j') image_type =".jpg"; + else if (inByte == 'b') image_type =".bmp"; + else if (inByte == 'p') image_type =".png"; + else if (inByte == 't') image_type =".tif"; +} + +boolean unicodeCheck(int unicode) +{ + if ( unicode >= '0' && unicode <= '9' ) return true; + if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true; + if ( unicode == '_' || unicode == '/' ) return true; + return false; +} + +String timeCode() +{ + String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_"; + timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second(); + return timeCode; +} + int renderPixels() { if ( serial.available() > 0 ) { @@ -310,7 +414,7 @@ int renderPixels() if (ypos>=tft_height) { ypos = 0; if ((int)percentage <100) { - while(progress_bar++ < 64) print(" "); + while (progress_bar++ < 64) print(" "); percent(100); } println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s"); @@ -363,24 +467,6 @@ void percent(float percentage) text(" [ " + (int)percentage + "% ]", 10, height-8); } -void saveScreenshot() -{ - println(); - String filename = "tft_screen_" + n + image_type; - println("Saving image as \"" + filename); - if (save_border) - { - PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border); - partialSave.save(filename); - } else { - PImage partialSave = get(x_offset, y_offset, tft_width, tft_height); - partialSave.save(filename); - } - - n = n + 1; - if (n>=max_images) n = 0; -} - boolean fadedImage() { int opacity = frameCount - drawLoopCount; // So we get increasing fade @@ -398,4 +484,42 @@ boolean fadedImage() return true; } return false; +} + +void drawButton(color buttonColor) +{ + stroke(0); + fill(buttonColor); + rect(500 - 100, 540 - 26, 80, 24); + textAlign(CENTER); + textSize(20); + fill(0); + if (running) text(" Pause ", 500 - 60, height-7); + else text(" Run ", 500 - 60, height-7); +} + +void buttonClicked() +{ + mouseClick = false; + if (running) { + running = false; + drawButton(buttonStopped); + System.err.println(""); + System.err.println("Stopped - click 'Run' button: "); + //noStroke(); + //fill(50); + //rect( (width - tft_width)/2, y_offset, tft_width, tft_height); + beginTime = millis() + 500; + dimmed = false; + state = 4; + } else { + running = true; + drawButton(buttonRunning); + } +} + +void mousePressed() { + if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) { + mouseClick = true; + } } \ No newline at end of file diff --git a/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino b/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino index d6f3cde..3e71f41 100644 --- a/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino +++ b/examples/ILI9341/TFT_Screen_Capture/processing_sketch.ino @@ -28,8 +28,8 @@ // option "Show Sketch Folder" or press Ctrl+K // Created by: Bodmer 5/3/17 -// Updated by: Bodmer 10/3/17 -// Version: 0.06 +// Updated by: Bodmer 12/3/17 +// Version: 0.07 // MIT licence applies, all text above must be included in derivative works @@ -53,7 +53,9 @@ boolean save_border = true; // Save the image with a border int border = 5; // Border pixel width # boolean fade = false; // Fade out image after saving # // # -int max_images = 10; // Maximum of numbered saved images before over-writing files # +int max_images = 100; // Maximum of numbered file images before over-writing files # +// # +int max_allowed = 1000; // Maximum number of save images allowed before a restart # // # // # End of the values to change for a particular setup # // ########################################################################################### @@ -77,6 +79,13 @@ color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color // TFT image frame greyscale value (dark grey) color frameColor = 42; +color buttonStopped = color(255, 0, 0); +color buttonRunning = color(128, 204, 206); +color buttonDimmed = color(180, 0, 0); +boolean dimmed = false; +boolean running = true; +boolean mouseClick = false; + int[] rgb = new int[3]; // Buffer for the colour bytes int indexRed = 0; // Colour byte index in the array int indexGreen = 1; @@ -93,6 +102,9 @@ int beginTime = 0; int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive int lastPixelTime = 0; // Time that "image send" command was sent +int requestTime = 0; +int requestCount = 0; + int state = 0; // State machine current state int progress_bar = 0; // Console progress bar dot count @@ -101,6 +113,7 @@ float percentage = 0; // Percentage of pixels received int saved_image_count = 0; // Stats - number of images processed int bad_image_count = 0; // Stats - number of images that had lost pixels +String filename = ""; int drawLoopCount = 0; // Used for the fade out @@ -134,42 +147,65 @@ void setup() { } void draw() { + + if (mouseClick) buttonClicked(); + switch(state) { case 0: // Init varaibles, send start request - tint(0, 0, 0, 255); - println(); - flushBuffer(); - println(""); - print("Ready: "); + if (running) { + tint(0, 0, 0, 255); + flushBuffer(); + println(""); + print("Ready: "); - xpos = 0; - ypos = 0; - serialCount = 0; - progress_bar = 0; - pixel_count = 0; - percentage = 0; - drawLoopCount = frameCount; - lastPixelTime = millis() + 1000; + xpos = 0; + ypos = 0; + serialCount = 0; + progress_bar = 0; + pixel_count = 0; + percentage = 0; + drawLoopCount = frameCount; + lastPixelTime = millis() + 1000; - state = 1; + state = 1; + } else { + if (millis() > beginTime) { + beginTime = millis() + 500; + dimmed = !dimmed; + if (dimmed) drawButton(buttonDimmed); + else drawButton(buttonStopped); + } + } break; case 1: // Console message, give server some time print("requesting image "); serial.write("S"); delay(10); - beginTime = millis() + 1000; + beginTime = millis(); + requestTime = millis() + 1000; + requestCount = 1; state = 2; break; case 2: // Get size and set start time for rendering duration report - if (millis() > beginTime) { - System.err.println(" - no response!"); - state = 0; + if (millis() > requestTime) { + requestCount++; + print("*"); + serial.clear(); + serial.write("S"); + if (requestCount > 32) { + requestCount = 0; + System.err.println(" - no response!"); + state = 0; + } + requestTime = millis() + 1000; } if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel + getFilename(); flushBuffer(); // Precaution in case image header size increases in later versions + lastPixelTime = millis() + 1000; beginTime = millis(); state = 3; } @@ -177,7 +213,7 @@ void draw() { case 3: // Request pixels and render returned RGB values state = renderPixels(); // State will change when all pixels are rendered - + // Request more pixels, changing the number requested allows the average transfer rate to be controlled // The pixel transfer rate is dependant on four things: // 1. The frame rate defined in this Processing sketch in setup() @@ -191,17 +227,17 @@ void draw() { //serial.write("RRRR"); // 4 x NPIXELS more //serial.write("RR"); // 2 x NPIXELS more //serial.write("R"); // 1 x NPIXELS more + if (!running) state = 4; break; case 4: // Pixel receive time-out, flush serial buffer - println(); flushBuffer(); state = 6; break; case 5: // Save the image to the sketch folder (Ctrl+K to access) saveScreenshot(); - saved_image_count++; + saved_image_count++; println("Saved image count = " + saved_image_count); if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count); drawLoopCount = frameCount; // Reset value ready for counting in step 6 @@ -240,13 +276,16 @@ void drawWindow() textSize(20); fill(0); text("Bodmer's TFT image viewer", width/2, height-6); + + if (running) drawButton(buttonRunning); + else drawButton(buttonStopped); } void flushBuffer() { //println("Clearing serial pipe after a time-out"); int clearTime = millis() + 50; - while ( millis() < clearTime ) serial.read(); + while ( millis() < clearTime ) serial.clear(); } boolean getSize() @@ -282,6 +321,71 @@ boolean getSize() return false; } +void saveScreenshot() +{ + println(); + if (saved_image_count < max_allowed) + { + if (filename == "") filename = "tft_screen_" + (n++); + filename = filename + image_type; + println("Saving image as \"" + filename + "\""); + if (save_border) + { + PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border); + partialSave.save(filename); + } else { + PImage partialSave = get(x_offset, y_offset, tft_width, tft_height); + partialSave.save(filename); + } + + if (n>=max_images) n = 0; + } + else + { + System.err.println(max_allowed + " saved image count exceeded, restart the sketch"); + } +} + +void getFilename() +{ + int readTime = millis() + 20; + int inByte = 0; + filename = ""; + while ( serial.available() > 0 && millis() < readTime && inByte != '.') + { + inByte = serial.read(); + if (inByte == ' ') inByte = '_'; + if ( unicodeCheck(inByte) ) filename += (char)inByte; + } + + inByte = serial.read(); + if (inByte == '@') filename += "_" + timeCode(); + else if (inByte == '#') filename += "_" + saved_image_count%100; + else if (inByte == '%') filename += "_" + millis(); + else if (inByte != '*') filename = ""; + + inByte = serial.read(); + if (inByte == 'j') image_type =".jpg"; + else if (inByte == 'b') image_type =".bmp"; + else if (inByte == 'p') image_type =".png"; + else if (inByte == 't') image_type =".tif"; +} + +boolean unicodeCheck(int unicode) +{ + if ( unicode >= '0' && unicode <= '9' ) return true; + if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true; + if ( unicode == '_' || unicode == '/' ) return true; + return false; +} + +String timeCode() +{ + String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_"; + timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second(); + return timeCode; +} + int renderPixels() { if ( serial.available() > 0 ) { @@ -318,7 +422,7 @@ int renderPixels() if (ypos>=tft_height) { ypos = 0; if ((int)percentage <100) { - while(progress_bar++ < 64) print(" "); + while (progress_bar++ < 64) print(" "); percent(100); } println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s"); @@ -371,24 +475,6 @@ void percent(float percentage) text(" [ " + (int)percentage + "% ]", 10, height-8); } -void saveScreenshot() -{ - println(); - String filename = "tft_screen_" + n + image_type; - println("Saving image as \"" + filename); - if (save_border) - { - PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border); - partialSave.save(filename); - } else { - PImage partialSave = get(x_offset, y_offset, tft_width, tft_height); - partialSave.save(filename); - } - - n = n + 1; - if (n>=max_images) n = 0; -} - boolean fadedImage() { int opacity = frameCount - drawLoopCount; // So we get increasing fade @@ -408,5 +494,42 @@ boolean fadedImage() return false; } +void drawButton(color buttonColor) +{ + stroke(0); + fill(buttonColor); + rect(500 - 100, 540 - 26, 80, 24); + textAlign(CENTER); + textSize(20); + fill(0); + if (running) text(" Pause ", 500 - 60, height-7); + else text(" Run ", 500 - 60, height-7); +} + +void buttonClicked() +{ + mouseClick = false; + if (running) { + running = false; + drawButton(buttonStopped); + System.err.println(""); + System.err.println("Stopped - click 'Run' button: "); + //noStroke(); + //fill(50); + //rect( (width - tft_width)/2, y_offset, tft_width, tft_height); + beginTime = millis() + 500; + dimmed = false; + state = 4; + } else { + running = true; + drawButton(buttonRunning); + } +} + +void mousePressed() { + if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) { + mouseClick = true; + } +} */ diff --git a/examples/ILI9341/TFT_Screen_Capture/screenServer.ino b/examples/ILI9341/TFT_Screen_Capture/screenServer.ino index 4c9ecbf..962d561 100644 --- a/examples/ILI9341/TFT_Screen_Capture/screenServer.ino +++ b/examples/ILI9341/TFT_Screen_Capture/screenServer.ino @@ -11,11 +11,13 @@ // Created by: Bodmer 27/1/17 // Updated by: Bodmer 10/3/17 -// Version: 0.06 +// Version: 0.07 // MIT licence applies, all text above must be included in derivative works - +//==================================================================================== +// Definitions +//==================================================================================== #define BAUD_RATE 250000 // Maximum Serial Monitor rate for other messages #define DUMP_BAUD_RATE 921600 // Rate used for screen dumps @@ -24,20 +26,46 @@ #define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format +// File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_" +// other ascii characters are stripped out by client, including / generates +// sub-directories +#define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified +#define FILE_TYPE "png" // jpg, bmp, png, tif are valid + +// Filename extension +// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing +// '@' and '%' will generate new unique filenames, so beware of cluttering up your +// hard drive with lots of images! The PC client sketch is set to limit the number of +// saved images to 1000 and will then prompt for a restart. +#define FILE_EXT '%' + // Number of pixels to send in a burst (minimum of 1), no benefit above 8 // NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s #define NPIXELS 8 // Must be integer division of both TFT width and TFT height - -// Start a screen dump server (serial or network) +//==================================================================================== +// Screen server call with no filename +//==================================================================================== +// Start a screen dump server (serial or network) - no filename specified boolean screenServer(void) +{ + // With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx + // where # is a number 0-9 and xxx is a file type specified below + return screenServer(DEFAULT_FILENAME); +} + +//==================================================================================== +// Screen server call with filename +//==================================================================================== +// Start a screen dump server (serial or network) - filename specified +boolean screenServer(String filename) { Serial.end(); // Stop the serial port (clears buffers too) Serial.begin(DUMP_BAUD_RATE); // Force baud rate to be high delay(0); // Equivalent to yield() for ESP8266; - boolean result = serialScreenServer(); // Screenshot serial port server - //boolean result = wifiScreenServer(); // Screenshot WiFi UDP port server (WIP) + boolean result = serialScreenServer(filename); // Screenshot serial port server + //boolean result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP) Serial.end(); // Stop the serial port (clears buffers too) Serial.begin(BAUD_RATE); // Return baud rate to normal @@ -50,10 +78,11 @@ boolean screenServer(void) return result; } -// Screenshot serial port server (Processing sketch acts as client) -boolean serialScreenServer(void) +//==================================================================================== +// Serial server function that sends the data to the client +//==================================================================================== +boolean serialScreenServer(String filename) { - // Precautionary receive buffer garbage flush for 50ms uint32_t clearTime = millis() + 50; while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266; @@ -78,16 +107,8 @@ boolean serialScreenServer(void) wait = false; // No need to wait anymore lastCmdTime = millis(); // Set last received command time - // Send screen size using a simple header with delimiters for client checks - Serial.write('W'); - Serial.write(tft.width() >> 8); - Serial.write(tft.width() & 0xFF); - Serial.write('H'); - Serial.write(tft.height() >> 8); - Serial.write(tft.height() & 0xFF); - Serial.write('Y'); - Serial.write(BITS_PER_PIXEL); - Serial.write('?'); + // Send screen size etc using a simple header with delimiters for client checks + sendParameters(filename); } } else @@ -138,9 +159,36 @@ boolean serialScreenServer(void) #endif } } - + Serial.flush(); // Make sure all pixel bytes have been despatched return true; } +//==================================================================================== +// Send screen size etc using a simple header with delimiters for client checks +//==================================================================================== +void sendParameters(String filename) +{ + Serial.write('W'); // Width + Serial.write(tft.width() >> 8); + Serial.write(tft.width() & 0xFF); + + Serial.write('H'); // Height + Serial.write(tft.height() >> 8); + Serial.write(tft.height() & 0xFF); + + Serial.write('Y'); // Bits per pixel (16 or 24) + Serial.write(BITS_PER_PIXEL); + + Serial.write('?'); // Filename next + Serial.print(filename); + + Serial.write('.'); // End of filename marker + + Serial.write(FILE_EXT); // Filename extension identifier + + Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t +} + +