536 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			536 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
| // This is a copy of the processing sketch that can be used to capture the images
 | |
| // Copy the sketch below and remove the /* and */ at the beginning and end.
 | |
| 
 | |
| // The sketch runs in Processing version 3.3 on a PC, it can be downloaded here:
 | |
| // https://processing.org/download/
 | |
| 
 | |
| /*
 | |
| 
 | |
| // This is a Processing sketch, see https://processing.org/ to download the IDE
 | |
| 
 | |
| // The sketch is a client that requests TFT screenshots from an Arduino board.
 | |
| // The Arduino must call a screenshot server function to respond with pixels.
 | |
| 
 | |
| // It has been created to work with the TFT_eSPI library here:
 | |
| // https://github.com/Bodmer/TFT_eSPI
 | |
| 
 | |
| // The sketch must only be run when the designated serial port is available and enumerated
 | |
| // otherwise the screenshot window may freeze and that process will need to be terminated
 | |
| // This is a limitation of the Processing environment and not the sketch.
 | |
| // If anyone knows how to determine if a serial port is available at start up the PM me
 | |
| // on (Bodmer) the Arduino forum.
 | |
| 
 | |
| // The block below contains variables that the user may need to change for a particular setup
 | |
| // As a minimum set the serial port and baud rate must be defined. The capture window is
 | |
| // automatically resized for landscape, portrait and different TFT resolutions.
 | |
| 
 | |
| // Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu
 | |
| // option "Show Sketch Folder" or press Ctrl+K
 | |
| 
 | |
| // Created by: Bodmer  5/3/17
 | |
| // Updated by: Bodmer 12/3/17
 | |
| // Version: 0.07
 | |
| 
 | |
| // MIT licence applies, all text above must be included in derivative works
 | |
| 
 | |
| 
 | |
| // ###########################################################################################
 | |
| // #                  These are the values to change for a particular setup                  #
 | |
| //                                                                                           #
 | |
| int serial_port = 0;     // Use enumerated value from list provided when sketch is run       #
 | |
| //                                                                                           #
 | |
| // On an Arduino Due Programming Port use a baud rate of:115200)                             #
 | |
| // On an Arduino Due Native USB Port use a baud rate of any value                            #
 | |
| int serial_baud_rate = 921600; //                                                            #
 | |
| //                                                                                           #
 | |
| // Change the image file type saved here, comment out all but one                            #
 | |
| //String image_type = ".jpg"; //                                                             #
 | |
| String image_type = ".png";   // Lossless compression                                        #
 | |
| //String image_type = ".bmp"; //                                                             #
 | |
| //String image_type = ".tif"; //                                                             #
 | |
| //                                                                                           #
 | |
| 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 = 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                    #
 | |
| // ###########################################################################################
 | |
| 
 | |
| // These are default values, this sketch obtains the actual values from the Arduino board
 | |
| int tft_width  = 480;    // default TFT width  (automatic - sent by Arduino)
 | |
| int tft_height = 480;    // default TFT height (automatic - sent by Arduino)
 | |
| int color_bytes = 2;     // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)
 | |
| 
 | |
| import processing.serial.*;
 | |
| 
 | |
| Serial serial;           // Create an instance called serial
 | |
| 
 | |
| int serialCount = 0;     // Count of colour bytes arriving
 | |
| 
 | |
| // Stage window graded background colours
 | |
| color bgcolor1 = color(0, 100, 104);      // Arduino IDE style background color 1
 | |
| color bgcolor2 = color(77, 183, 187);     // Arduino IDE style background color 2
 | |
| //color bgcolor2 = color(255, 255, 255);  // White
 | |
| 
 | |
| // 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;
 | |
| int indexBlue  = 2;
 | |
| 
 | |
| int n = 0;
 | |
| 
 | |
| int x_offset = (500 - tft_width) /2; // Image offsets in the window
 | |
| int y_offset = 20;
 | |
| 
 | |
| int xpos = 0, ypos = 0; // Current pixel position
 | |
| 
 | |
| 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
 | |
| int   pixel_count  = 0; // Number of pixels read for 1 screen
 | |
| 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
 | |
| 
 | |
| void setup() {
 | |
| 
 | |
|   size(500, 540);  // Stage size, can handle 480 pixels wide screen
 | |
|   noStroke();      // No border on the next thing drawn
 | |
|   noSmooth();      // No anti-aliasing to avoid adjacent pixel colour merging
 | |
| 
 | |
|   // Graded background and title
 | |
|   drawWindow();
 | |
| 
 | |
|   frameRate(2000); // High frame rate so draw() loops fast
 | |
| 
 | |
|   // Print a list of the available serial ports
 | |
|   println("-----------------------");
 | |
|   println("Available Serial Ports:");
 | |
|   println("-----------------------");
 | |
|   printArray(Serial.list());
 | |
|   println("-----------------------");
 | |
| 
 | |
|   print("Port currently used: [");
 | |
|   print(serial_port);
 | |
|   println("]");
 | |
| 
 | |
|   String portName = Serial.list()[serial_port];
 | |
| 
 | |
|   serial = new Serial(this, portName, serial_baud_rate);
 | |
| 
 | |
|   state = 99;
 | |
| }
 | |
| 
 | |
| void draw() {
 | |
| 
 | |
|   if (mouseClick) buttonClicked();
 | |
| 
 | |
|   switch(state) {
 | |
| 
 | |
|   case 0: // Init varaibles, send start request
 | |
|     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;
 | |
| 
 | |
|       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();
 | |
|     requestTime = millis() + 1000;
 | |
|     requestCount = 1;
 | |
|     state = 2;
 | |
|     break;
 | |
| 
 | |
|   case 2: // Get size and set start time for rendering duration report
 | |
|     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;
 | |
|     }
 | |
|     break;
 | |
| 
 | |
|   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()
 | |
|     //    2. The baud rate of the serial link (~10 bit periods per byte)
 | |
|     //    3. The number of request bytes 'R' sent in the lines below
 | |
|     //    4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)
 | |
| 
 | |
|     //serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more
 | |
|     serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more
 | |
|     //serial.write("RRRRRRRR"); // 8 x NPIXELS more
 | |
|     //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
 | |
|     flushBuffer();
 | |
|     state = 6;
 | |
|     break;
 | |
| 
 | |
|   case 5: // Save the image to the sketch folder (Ctrl+K to access)
 | |
|     saveScreenshot();
 | |
|     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
 | |
|     state = 6;
 | |
|     break;
 | |
| 
 | |
|   case 6: // Fade the old image if enabled
 | |
|     if ( fadedImage() == true ) state = 0; // Go to next state when image has faded
 | |
|     break;
 | |
| 
 | |
|   case 99: // Draw image viewer window
 | |
|     drawWindow();
 | |
|     delay(50); // Delay here seems to be required for the IDE console to get ready
 | |
|     state = 0;
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     println("");
 | |
|     System.err.println("Error state reached - check sketch!");
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void drawWindow()
 | |
| {
 | |
|   // Graded background in Arduino colours
 | |
|   for (int i = 0; i < height - 25; i++) {
 | |
|     float inter = map(i, 0, height - 25, 0, 1);
 | |
|     color c = lerpColor(bgcolor1, bgcolor2, inter);
 | |
|     stroke(c);
 | |
|     line(0, i, 500, i);
 | |
|   }
 | |
|   fill(bgcolor2);
 | |
|   rect( 0, height-25, width-1, 24);
 | |
|   textAlign(CENTER);
 | |
|   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.clear();
 | |
| }
 | |
| 
 | |
| boolean getSize()
 | |
| {
 | |
|   if ( serial.available() > 6 ) {
 | |
|     println();
 | |
|     char code = (char)serial.read();
 | |
|     if (code == 'W') {
 | |
|       tft_width = serial.read()<<8 | serial.read();
 | |
|     }
 | |
|     code = (char)serial.read();
 | |
|     if (code == 'H') {
 | |
|       tft_height = serial.read()<<8 | serial.read();
 | |
|     }
 | |
|     code = (char)serial.read();
 | |
|     if (code == 'Y') {
 | |
|       int bits_per_pixel = (char)serial.read();
 | |
|       if (bits_per_pixel == 24) color_bytes = 3;
 | |
|       else color_bytes = 2;
 | |
|     }
 | |
|     code = (char)serial.read();
 | |
|     if (code == '?') {
 | |
|       drawWindow();
 | |
| 
 | |
|       x_offset = (500 - tft_width) /2;
 | |
|       tint(0, 0, 0, 255);
 | |
|       noStroke();
 | |
|       fill(frameColor);
 | |
|       rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   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 ) {
 | |
| 
 | |
|     // Add the latest byte from the serial port to array:
 | |
|     while (serial.available()>0)
 | |
|     {
 | |
|       rgb[serialCount++] = serial.read();
 | |
| 
 | |
|       // If we have 3 colour bytes:
 | |
|       if ( serialCount >= color_bytes ) {
 | |
|         serialCount = 0;
 | |
|         pixel_count++;
 | |
|         if (color_bytes == 3)
 | |
|         {
 | |
|           stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);
 | |
|         } else
 | |
|         { // Can cater for various byte orders
 | |
|           //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));
 | |
|           //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));
 | |
|           stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);
 | |
|           //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);
 | |
|         }
 | |
|         // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice
 | |
|         point(xpos + x_offset, ypos + y_offset);
 | |
|         //point(xpos + x_offset, ypos + y_offset);
 | |
| 
 | |
|         lastPixelTime = millis();
 | |
|         xpos++;
 | |
|         if (xpos >= tft_width) {
 | |
|           xpos = 0; 
 | |
|           progressBar();
 | |
|           ypos++;
 | |
|           if (ypos>=tft_height) {
 | |
|             ypos = 0;
 | |
|             if ((int)percentage <100) {
 | |
|               while (progress_bar++ < 64) print(" ");
 | |
|               percent(100);
 | |
|             }
 | |
|             println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
 | |
|             return 5;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     if (millis() > (lastPixelTime + pixelWaitTime))
 | |
|     {
 | |
|       println("");
 | |
|       System.err.println(pixelWaitTime + "ms time-out for pixels exceeded...");
 | |
|       if (pixel_count > 0) {
 | |
|         bad_image_count++;
 | |
|         System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count));
 | |
|         System.err.println(", corrupted image not saved");
 | |
|         System.err.println("Good image count = " + saved_image_count);
 | |
|         System.err.println(" Bad image count = " + bad_image_count);
 | |
|       }
 | |
|       return 4;
 | |
|     }
 | |
|   }
 | |
|   return 3;
 | |
| }
 | |
| 
 | |
| void progressBar()
 | |
| {
 | |
|   progress_bar++;
 | |
|   print(".");
 | |
|   if (progress_bar >63)
 | |
|   {
 | |
|     progress_bar = 0;
 | |
|     percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);
 | |
|     percent(percentage);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void percent(float percentage)
 | |
| {
 | |
|   if (percentage > 100) percentage = 100;
 | |
|   println(" [ " + (int)percentage + "% ]");
 | |
|   textAlign(LEFT);
 | |
|   textSize(16);
 | |
|   noStroke();
 | |
|   fill(bgcolor2);
 | |
|   rect(10, height - 25, 70, 20);
 | |
|   fill(0);
 | |
|   text(" [ " + (int)percentage + "% ]", 10, height-8);
 | |
| }
 | |
| 
 | |
| boolean fadedImage()
 | |
| {
 | |
|   int opacity = frameCount - drawLoopCount;  // So we get increasing fade
 | |
|   if (fade)
 | |
|   {
 | |
|     tint(255, opacity);
 | |
|     //image(tft_img, x_offset, y_offset);
 | |
|     noStroke();
 | |
|     fill(50, 50, 50, opacity);
 | |
|     rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
 | |
|     delay(10);
 | |
|   }
 | |
|   if (opacity > 50)       // End fade after 50 cycles
 | |
|   {
 | |
|     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;
 | |
|   }
 | |
| }
 | |
| 
 | |
| */
 |