acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

Screenshot_client.pde (17047B)

      1 // This is a Processing sketch, see https://processing.org/ to download the IDE
      2 
      3 // The sketch is a client that requests TFT screenshots from an Arduino board.
      4 // The Arduino must call a screenshot server function to respond with pixels.
      5 
      6 // It has been created to work with the TFT_eSPI library here:
      7 // https://github.com/Bodmer/TFT_eSPI
      8 
      9 // The sketch must only be run when the designated serial port is available and enumerated
     10 // otherwise the screenshot window may freeze and that process will need to be terminated
     11 // This is a limitation of the Processing environment and not the sketch.
     12 // If anyone knows how to determine if a serial port is available at start up the PM me
     13 // on (Bodmer) the Arduino forum.
     14 
     15 // The block below contains variables that the user may need to change for a particular setup
     16 // As a minimum set the serial port and baud rate must be defined. The capture window is
     17 // automatically resized for landscape, portrait and different TFT resolutions.
     18 
     19 // Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu
     20 // option "Show Sketch Folder" or press Ctrl+K
     21 
     22 // Created by: Bodmer  5/3/17
     23 // Updated by: Bodmer 12/3/17
     24 // Version: 0.07
     25 
     26 // MIT licence applies, all text above must be included in derivative works
     27 
     28 
     29 // ###########################################################################################
     30 // #                  These are the values to change for a particular setup                  #
     31 //                                                                                           #
     32 int serial_port = 0;     // Use enumerated value from list provided when sketch is run       #
     33 //                                                                                           #
     34 // On an Arduino Due Programming Port use a baud rate of:115200)                             #
     35 // On an Arduino Due Native USB Port use a baud rate of any value                            #
     36 int serial_baud_rate = 921600; //                                                            #
     37 //                                                                                           #
     38 // Change the image file type saved here, comment out all but one                            #
     39 //String image_type = ".jpg"; //                                                             #
     40 String image_type = ".png";   // Lossless compression                                        #
     41 //String image_type = ".bmp"; //                                                             #
     42 //String image_type = ".tif"; //                                                             #
     43 //                                                                                           #
     44 boolean save_border = true;   // Save the image with a border                                #
     45 int border = 5;               // Border pixel width                                          #
     46 boolean fade = false;         // Fade out image after saving                                 #
     47 //                                                                                           #
     48 int max_images = 100; // Maximum of numbered file images before over-writing files           #
     49 //                                                                                           #
     50 int max_allowed  = 1000; // Maximum number of save images allowed before a restart           #
     51 //                                                                                           #
     52 // #                   End of the values to change for a particular setup                    #
     53 // ###########################################################################################
     54 
     55 // These are default values, this sketch obtains the actual values from the Arduino board
     56 int tft_width  = 480;    // default TFT width  (automatic - sent by Arduino)
     57 int tft_height = 480;    // default TFT height (automatic - sent by Arduino)
     58 int color_bytes = 2;     // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)
     59 
     60 import processing.serial.*;
     61 
     62 Serial serial;           // Create an instance called serial
     63 
     64 int serialCount = 0;     // Count of colour bytes arriving
     65 
     66 // Stage window graded background colours
     67 color bgcolor1 = color(0, 100, 104);			// Arduino IDE style background color 1
     68 color bgcolor2 = color(77, 183, 187);     // Arduino IDE style background color 2
     69 //color bgcolor2 = color(255, 255, 255);  // White
     70 
     71 // TFT image frame greyscale value (dark grey)
     72 color frameColor = 42;
     73 
     74 color buttonStopped = color(255, 0, 0);
     75 color buttonRunning = color(128, 204, 206);
     76 color buttonDimmed  = color(180, 0, 0);
     77 boolean dimmed   = false;
     78 boolean running  = true;
     79 boolean mouseClick = false;
     80 
     81 int[] rgb = new int[3]; // Buffer for the colour bytes
     82 int indexRed   = 0;     // Colour byte index in the array
     83 int indexGreen = 1;
     84 int indexBlue  = 2;
     85 
     86 int n = 0;
     87 
     88 int x_offset = (500 - tft_width) /2; // Image offsets in the window
     89 int y_offset = 20;
     90 
     91 int xpos = 0, ypos = 0; // Current pixel position
     92 
     93 int beginTime     = 0;
     94 int pixelWaitTime = 1000;  // Maximum 1000ms wait for image pixels to arrive
     95 int lastPixelTime = 0;     // Time that "image send" command was sent
     96 
     97 int requestTime = 0;
     98 int requestCount = 0;
     99 
    100 int state = 0;  // State machine current state
    101 
    102 int   progress_bar = 0; // Console progress bar dot count
    103 int   pixel_count  = 0; // Number of pixels read for 1 screen
    104 float percentage   = 0; // Percentage of pixels received
    105 
    106 int  saved_image_count = 0; // Stats - number of images processed
    107 int  bad_image_count  = 0;  // Stats - number of images that had lost pixels
    108 String filename = "";
    109 
    110 int drawLoopCount = 0;      // Used for the fade out
    111 
    112 void setup() {
    113 
    114   size(500, 540);  // Stage size, can handle 480 pixels wide screen
    115   noStroke();      // No border on the next thing drawn
    116   noSmooth();      // No anti-aliasing to avoid adjacent pixel colour merging
    117 
    118   // Graded background and title
    119   drawWindow();
    120 
    121   frameRate(2000); // High frame rate so draw() loops fast
    122 
    123   // Print a list of the available serial ports
    124   println("-----------------------");
    125   println("Available Serial Ports:");
    126   println("-----------------------");
    127   printArray(Serial.list());
    128   println("-----------------------");
    129 
    130   print("Port currently used: [");
    131   print(serial_port);
    132   println("]");
    133 
    134   String portName = Serial.list()[serial_port];
    135 
    136   serial = new Serial(this, portName, serial_baud_rate);
    137 
    138   state = 99;
    139 }
    140 
    141 void draw() {
    142 
    143   if (mouseClick) buttonClicked();
    144 
    145   switch(state) {
    146 
    147   case 0: // Init varaibles, send start request
    148     if (running) {
    149       tint(0, 0, 0, 255);
    150       flushBuffer();
    151       println("");
    152       print("Ready: ");
    153 
    154       xpos = 0;
    155       ypos = 0;
    156       serialCount = 0;
    157       progress_bar = 0;
    158       pixel_count = 0;
    159       percentage   = 0;
    160       drawLoopCount = frameCount;
    161       lastPixelTime = millis() + 1000;
    162 
    163       state = 1;
    164     } else {
    165       if (millis() > beginTime) {
    166         beginTime = millis() + 500;
    167         dimmed = !dimmed;
    168         if (dimmed) drawButton(buttonDimmed);
    169         else drawButton(buttonStopped);
    170       }
    171     }
    172     break;
    173 
    174   case 1: // Console message, give server some time
    175     print("requesting image ");
    176     serial.write("S");
    177     delay(10);
    178     beginTime = millis();
    179     requestTime = millis() + 1000;
    180     requestCount = 1;
    181     state = 2;
    182     break;
    183 
    184   case 2: // Get size and set start time for rendering duration report
    185     if (millis() > requestTime) {
    186       requestCount++;
    187       print("*");
    188       serial.clear();
    189       serial.write("S");
    190       if (requestCount > 32) {
    191         requestCount = 0;
    192         System.err.println(" - no response!");
    193         state = 0;
    194       }
    195       requestTime = millis() + 1000;
    196     }
    197     if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel
    198       getFilename();
    199       flushBuffer(); // Precaution in case image header size increases in later versions
    200       lastPixelTime = millis() + 1000;
    201       beginTime = millis();
    202       state = 3;
    203     }
    204     break;
    205 
    206   case 3: // Request pixels and render returned RGB values
    207     state = renderPixels(); // State will change when all pixels are rendered
    208 
    209     // Request more pixels, changing the number requested allows the average transfer rate to be controlled
    210     // The pixel transfer rate is dependant on four things:
    211     //    1. The frame rate defined in this Processing sketch in setup()
    212     //    2. The baud rate of the serial link (~10 bit periods per byte)
    213     //    3. The number of request bytes 'R' sent in the lines below
    214     //    4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)
    215 
    216     //serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more
    217     serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more
    218     //serial.write("RRRRRRRR"); // 8 x NPIXELS more
    219     //serial.write("RRRR"); // 4 x NPIXELS more
    220     //serial.write("RR"); // 2 x NPIXELS more
    221     //serial.write("R"); // 1 x NPIXELS more
    222     if (!running) state = 4;
    223     break;
    224 
    225   case 4: // Pixel receive time-out, flush serial buffer
    226     flushBuffer();
    227     state = 6;
    228     break;
    229 
    230   case 5: // Save the image to the sketch folder (Ctrl+K to access)
    231     saveScreenshot();
    232     saved_image_count++;
    233     println("Saved image count = " + saved_image_count);
    234     if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count);
    235     drawLoopCount = frameCount; // Reset value ready for counting in step 6
    236     state = 6;
    237     break;
    238 
    239   case 6: // Fade the old image if enabled
    240     if ( fadedImage() == true ) state = 0; // Go to next state when image has faded
    241     break;
    242 
    243   case 99: // Draw image viewer window
    244     drawWindow();
    245     delay(50); // Delay here seems to be required for the IDE console to get ready
    246     state = 0;
    247     break;
    248 
    249   default:
    250     println("");
    251     System.err.println("Error state reached - check sketch!");
    252     break;
    253   }
    254 }
    255 
    256 void drawWindow()
    257 {
    258   // Graded background in Arduino colours
    259   for (int i = 0; i < height - 25; i++) {
    260     float inter = map(i, 0, height - 25, 0, 1);
    261     color c = lerpColor(bgcolor1, bgcolor2, inter);
    262     stroke(c);
    263     line(0, i, 500, i);
    264   }
    265   fill(bgcolor2);
    266   rect( 0, height-25, width-1, 24);
    267   textAlign(CENTER);
    268   textSize(20);
    269   fill(0);
    270   text("Bodmer's TFT image viewer", width/2, height-6);
    271 
    272   if (running) drawButton(buttonRunning);
    273   else drawButton(buttonStopped);
    274 }
    275 
    276 void flushBuffer()
    277 {
    278   //println("Clearing serial pipe after a time-out");
    279   int clearTime = millis() + 50;
    280   while ( millis() < clearTime ) serial.clear();
    281 }
    282 
    283 boolean getSize()
    284 {
    285   if ( serial.available() > 6 ) {
    286     println();
    287     char code = (char)serial.read();
    288     if (code == 'W') {
    289       tft_width = serial.read()<<8 | serial.read();
    290     }
    291     code = (char)serial.read();
    292     if (code == 'H') {
    293       tft_height = serial.read()<<8 | serial.read();
    294     }
    295     code = (char)serial.read();
    296     if (code == 'Y') {
    297       int bits_per_pixel = (char)serial.read();
    298       if (bits_per_pixel == 24) color_bytes = 3;
    299       else color_bytes = 2;
    300     }
    301     code = (char)serial.read();
    302     if (code == '?') {
    303       drawWindow();
    304 
    305       x_offset = (500 - tft_width) /2;
    306       tint(0, 0, 0, 255);
    307       noStroke();
    308       fill(frameColor);
    309       rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);
    310       return true;
    311     }
    312   }
    313   return false;
    314 }
    315 
    316 void saveScreenshot()
    317 {
    318   println();
    319   if (saved_image_count < max_allowed)
    320   {
    321   if (filename == "") filename = "tft_screen_" + (n++);
    322   filename = filename  + image_type;
    323   println("Saving image as \"" + filename + "\"");
    324   if (save_border)
    325   {
    326     PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
    327     partialSave.save(filename);
    328   } else {
    329     PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
    330     partialSave.save(filename);
    331   }
    332 
    333   if (n>=max_images) n = 0;
    334   }
    335   else
    336   {
    337     System.err.println(max_allowed + " saved image count exceeded, restart the sketch");
    338   }
    339 }
    340 
    341 void getFilename()
    342 {
    343   int readTime = millis() + 20;
    344   int inByte = 0;
    345   filename = "";
    346   while ( serial.available() > 0 && millis() < readTime && inByte != '.')
    347   {
    348     inByte = serial.read();
    349     if (inByte == ' ') inByte = '_';
    350     if ( unicodeCheck(inByte) ) filename += (char)inByte;
    351   }
    352 
    353   inByte = serial.read();
    354        if (inByte == '@') filename += "_" + timeCode();
    355   else if (inByte == '#') filename += "_" + saved_image_count%100;
    356   else if (inByte == '%') filename += "_" + millis();
    357   else if (inByte != '*') filename  = "";
    358 
    359   inByte = serial.read();
    360        if (inByte == 'j') image_type =".jpg";
    361   else if (inByte == 'b') image_type =".bmp";
    362   else if (inByte == 'p') image_type =".png";
    363   else if (inByte == 't') image_type =".tif";
    364 }
    365 
    366 boolean unicodeCheck(int unicode)
    367 {
    368   if (  unicode >= '0' && unicode <= '9' ) return true;
    369   if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;
    370   if (  unicode == '_' || unicode == '/' ) return true;
    371   return false;
    372 }
    373 
    374 String timeCode()
    375 {
    376  String timeCode  = (int)year() + "_" + (int)month()  + "_" + (int)day() + "_";
    377         timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second(); 
    378  return timeCode;
    379 }
    380 
    381 int renderPixels()
    382 {
    383   if ( serial.available() > 0 ) {
    384 
    385     // Add the latest byte from the serial port to array:
    386     while (serial.available()>0)
    387     {
    388       rgb[serialCount++] = serial.read();
    389 
    390       // If we have 3 colour bytes:
    391       if ( serialCount >= color_bytes ) {
    392         serialCount = 0;
    393         pixel_count++;
    394         if (color_bytes == 3)
    395         {
    396           stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);
    397         } else
    398         { // Can cater for various byte orders
    399           //stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));
    400           //stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));
    401           stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);
    402           //stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);
    403         }
    404         // We get some pixel merge aliasing if smooth() is defined, so draw pixel twice
    405         point(xpos + x_offset, ypos + y_offset);
    406         //point(xpos + x_offset, ypos + y_offset);
    407 
    408         lastPixelTime = millis();
    409         xpos++;
    410         if (xpos >= tft_width) {
    411           xpos = 0; 
    412           progressBar();
    413           ypos++;
    414           if (ypos>=tft_height) {
    415             ypos = 0;
    416             if ((int)percentage <100) {
    417               while (progress_bar++ < 64) print(" ");
    418               percent(100);
    419             }
    420             println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
    421             return 5;
    422           }
    423         }
    424       }
    425     }
    426   } else
    427   {
    428     if (millis() > (lastPixelTime + pixelWaitTime))
    429     {
    430       println("");
    431       System.err.println(pixelWaitTime + "ms time-out for pixels exceeded...");
    432       if (pixel_count > 0) {
    433         bad_image_count++;
    434         System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count));
    435         System.err.println(", corrupted image not saved");
    436         System.err.println("Good image count = " + saved_image_count);
    437         System.err.println(" Bad image count = " + bad_image_count);
    438       }
    439       return 4;
    440     }
    441   }
    442   return 3;
    443 }
    444 
    445 void progressBar()
    446 {
    447   progress_bar++;
    448   print(".");
    449   if (progress_bar >63)
    450   {
    451     progress_bar = 0;
    452     percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);
    453     percent(percentage);
    454   }
    455 }
    456 
    457 void percent(float percentage)
    458 {
    459   if (percentage > 100) percentage = 100;
    460   println(" [ " + (int)percentage + "% ]");
    461   textAlign(LEFT);
    462   textSize(16);
    463   noStroke();
    464   fill(bgcolor2);
    465   rect(10, height - 25, 70, 20);
    466   fill(0);
    467   text(" [ " + (int)percentage + "% ]", 10, height-8);
    468 }
    469 
    470 boolean fadedImage()
    471 {
    472   int opacity = frameCount - drawLoopCount;  // So we get increasing fade
    473   if (fade)
    474   {
    475     tint(255, opacity);
    476     //image(tft_img, x_offset, y_offset);
    477     noStroke();
    478     fill(50, 50, 50, opacity);
    479     rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
    480     delay(10);
    481   }
    482   if (opacity > 50)       // End fade after 50 cycles
    483   {
    484     return true;
    485   }
    486   return false;
    487 }
    488 
    489 void drawButton(color buttonColor)
    490 {
    491   stroke(0);
    492   fill(buttonColor);
    493   rect(500 - 100, 540 - 26, 80, 24);
    494   textAlign(CENTER);
    495   textSize(20);
    496   fill(0);
    497   if (running) text(" Pause ", 500 - 60, height-7);
    498   else text(" Run ", 500 - 60, height-7);
    499 }
    500 
    501 void buttonClicked()
    502 {
    503   mouseClick = false;
    504   if (running) {
    505     running = false;
    506     drawButton(buttonStopped);
    507     System.err.println("");
    508     System.err.println("Stopped - click 'Run' button: ");
    509     //noStroke();
    510     //fill(50);
    511     //rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
    512     beginTime = millis() + 500;
    513     dimmed = false;
    514     state = 4;
    515   } else {
    516     running = true;
    517     drawButton(buttonRunning);
    518   }
    519 }
    520 
    521 void mousePressed() {
    522   if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {
    523     mouseClick = true;
    524   }
    525 }