Capture Sparki's LCD Screen as an Image



Introduction:

I'm going to be writing some tutorials on using Sparki, and I wanted an easy way to capture his LCD screen output as an image that I could add to my tutorials.

The idea came to me that I should write a little function to run on Sparki that would send the screen data over the Serial port connection to a Processing sketch running on my computer.

You'll often see tutorials showing how to connect Sparki (or an Arduino board) to a Processing sketch running on the computer. The Sparki/Arduino programming environment actually derives from the programming environment for Processing. That's because they were both developed to help artists, designers, and hobbyists get into computer art and interactive art more easily.

So in this tutorial, I'll show you the Sparki code to send the screen data, and the Processing code to receive it and write an image to your computer's hard drive.

For any Sparki program where you want to save the LCD contents as an image, you need a few things to happen:

  1. You need to have something drawn on Sparki's LCD screen that you want to save.
  2. You need to be able to read all the pixel data.
  3. You need to be able to put it in a format that you can send over a Serial connection.

So let's see how to do that.





Step 1:

Sparki Code - Drawing to the Screen (1)

There are a couple of ways to go about getting something worthwhile on the screen.

The first solution is to do nothing. By default, if you don't write anything to the LCD screen in your program, Sparki will display the Sparki logo. You could use that as a test. You do need to make at least one function call to Sparki (sparki.begin(); in this example), otherwise the compiler will see you're not doing anything with Sparki and will "optimize" the Sparki library out of existence. For example, try this code in the SparkiDuino editor:

#include <Sparki.h>

void setup() {
 sparki.begin();
}

void loop() {
}

This program just launches Sparki's default initialization routine with sparki.begin(). Among other things, that writes the Sparki logo to the LCD.

 


Step 2:

Sparki Code - Drawing to the Screen (2)

The second solution would be to draw your own images to the screen. All the drawing routines are given in (X,Y) coordinates. X coordinates range from 0 on the left of the screen to 127 on the right. Y coordinates range from 0 at the top of the screen to 63 at the bottom. So the point at coordinate (0,0) is at the top left of the screen, and the point at coordinate (127,63) is at the bottom left. Point (63,31) is approximately in the middle of the screen.

Here's a little program that demonstrates a few of the drawing functions available for Sparki's LCD screen:

#include <Sparki.h>

void setup() {
}

void loop() {
  sparki.clearLCD(); //remove all the drawings
  sparki.drawLine(0,0,  127,63); //draw a diagonal line from 0,0 to 127,63
  sparki.drawLine(0,63,  127,0); //draw a diagonal line from 0,63 to 127,0
  sparki.updateLCD();
  delay(2000); //wait 2 seconds

  sparki.clearLCD(); //remove all the drawings
  sparki.drawRect(15,15,  98,34); //draw a rectangle from 15,15 that is 93 pixels wide and 34 high
  sparki.drawRectFilled(25,25,  78,14); //draw a filled rectangle
  sparki.updateLCD();
  delay(2000); //wait 2 seconds

  sparki.clearLCD(); //remove all the drawings
  sparki.drawCircle(63,31,  20); //draw a circle of radius 20 centered on point 63,31
  sparki.drawCircleFilled(63,31,  10); //draw a filled circle
  sparki.updateLCD();
  delay(2000); //wait 2 seconds
}

 The drawings that this sample code produces can be seen in the images above. You may note that the "circle" looks more like an oval. That's because each pixel in the drawing is actually taller than it is wide. Each pixel is about 1 unit wide and 1.5 units high. Your computer on the other hand probably has pixels that are 1 unit wide and 1 unit high. This is called the pixel aspect ratio. Your computer has a ratio of 1:1 (1 to 1) while the Sparki LCD screen has a ratio of 1:1.5 (1 to 1.5).


Step 3:

Sparki Code - Reading the Screen Data

Next we need a way to read the pixels on Sparki's LCD screen to determine if they are turned on (white) or turned off (blue). This turns out to be pretty easy, because Sparki comes with a built in readPixel(x, y) function. We just need to look at every pixel on the screen, one by one.

The standard way to do something with every pixel on a display is to use a nested loop. One loop looks at every row (y-coordinate) from the top (0) to the bottom (63), while a second loop inside that one looks at every column (x-coordinate) from the left (0) to the right (127).

Here's how that would look in code:

void sendScreenDataToProcessing()
{
  for(int y=0; y<64; y++)
  {
    for(int x=0; x<128; x++)
    {
	  byte pixel = sparki.readPixel(x, y);
    }
  }
}

We put the new code inside a function sendScreenDataToProcessing(). That way we can call this function from wherever we need it in our main loop() function or elsewhere.

The y variable starts out as 0. The inner (x) loop then iterates through every value of x from 0 to 127, reading the value of the pixel at (x,y) for each new value of x. Only after x has been read from 0 to 127 does the outer (y) loop increment y to 1. The inner loop then reads pixels for every value of x from 0 to 127 again, before the outer loop increments y to 2. And so on all the way through y=63, x=127.


Step 4:

Sparki Code - Sending the Data

Next we need to write code that lets Sparki send the screen data to the computer over the Serial port connection. Let's start out with the simplest code that just displays the Sparki logo, and we'll add our sendScreenDataToProcessing() function at the bottom:

#include <Sparki.h>

void setup() {
 sparki.begin();
}

void loop() {
}

void sendScreenDataToProcessing()
{
  for(int y=0; y<64; y++)
  {
    for(int x=0; x<128; x++)
    {
      byte pixel = sparki.readPixel(x, y);
    }
  }
}

Now we need to add in code to let Sparki connect to the Serial port and send data to whatever is listening on the other end (in this case, the Processing sketch that we will write next).

First, we need to add code to connect to the Serial port. That code goes in the setup() function and it looks like this:

Serial.begin(9600);

That tells Sparki to open a connection on the Serial port with a baud rate (communication speed) of 9600 bits per second. The Serial connection in the Processing sketch will have to use the same baud rate or Sparki and Processing won't be able to communicate well (if at all).

Next, we need to do something with the pixel data that we read in the sendScreenDataToProcessing() function.

How should we send the data? I decided to send the data as ASCII text strings.

  1. The image transmission will start with <img> followed by return character.
        Serial.println("<img>");
  2. Data will be sent a row at a time.
  3. Every white pixel (on) will be represented by an asterisk (*).
          if(pixel == 1) //if the pixel is white / on / true / 1
          {
           Serial.print('*'); //send a *
          }
  4. Every blue pixel (off) will be represented by a space.
          else //if the pixel is blue / off / false / 0
          {
           Serial.print(' '); //send a blank space
          }
  5. Each row of data will end with a return character.
        Serial.println();
  6. The image transmission will end with </img> followed by a return character.
           Serial.println("</img>");

We need to put a call to our method inside the loop() function so that it will be run every time Sparki loops. But because I only want to take screenshots of Sparki's LCD screen when I decide to, I'll put some logic in that only calls the function when I push a button on Sparki's remote control:
 

//The numeric code for the "open gripper" button on the Sparki remote is "9"
//That button also looks a little bit like a "Transmit" symbol.
const int BUTTON_OPEN = 9;

void loop() {
  //Read the infrared remote. 
  //If the "open gripper" button is pressed, 
  //send the screen data to Processing.
  if(sparki.readIR() == BUTTON_OPEN) 
  {
    sendScreenDataToProcessing();
  }
}

Once we add those pieces into our existing code, this is what we get:

#include <Sparki.h>

void setup() {
 sparki.begin();
 Serial.begin(9600);
}

//The numeric code for the "open gripper" button on the Sparki remote is "9"
//That button also looks a little bit like a "Transmit" symbol.
const int BUTTON_OPEN = 9;

void loop() {
  //Read the infrared remote. 
  //If the "open gripper"/"transmit" button is pressed, 
  //send the screen data to Processing.
  if(sparki.readIR() == BUTTON_OPEN) 
  {
    sendScreenDataToProcessing();
  }
}

void sendScreenDataToProcessing()
{
  Serial.println("<img>");   //start the image transmission by sending <img>
  for(int y=0; y<64; y++)    //loop over every row
  {
    for(int x=0; x<128; x++) //loop over every column in the current row
    {
      byte pixel = sparki.readPixel(x, y); //read the current pixel
      if(pixel == 1) //if the pixel is white / on / true / 1
      {
        Serial.print('*'); //send a *
      }
      else //if the pixel is blue / off / false / 0
      {
        Serial.print(' '); //send a blank space
      }
    }
    Serial.println(); //after every row, end the line with a return character
  }
  Serial.println("</img>"); //end the image transmission by sending </img>
}

That's a pretty good start. Next we'll see how to test the code even before we write our Processing sketch.


Step 5:

Sparki Code - Testing with the SparkiDuino Serial Monitor

SparkiDuino (or the Arduino code editor) come with a tool called the Serial Monitor. This lets you listen to messages that your Sparki sends over the Serial connection.

Once you have compiled your code and sent it to your Sparki, go the the Tools menu and select Serial Monitor.

Your Sparki should be showing the Sparki logo on its screen at this point. Press the "open gripper" button that we are using for "transmit". When you do, you should see that Sparki transmits the screen data on the Serial connection as spaces and asterisks scroll across the Serial Monitor screen. (See the image above.)

You should also be able to see that the image is surrounded by <img> </img> markers. Those will help tell the Processing sketch when to start and stop reading an image from Sparki.

If you have any problems, engage your troubleshooting methods. Make sure your code is entered correctly. And make sure that the baud rate selected at the bottom left of the Serial Monitor screen matches the baud rate in the Sparki code (9600).

One more thing. You may notice that when you push the button to send the screen data, the screen data is transmitted several times to the Serial Monitor. That is because in the time it takes you to push the remote control button and then rapidly remove your thumb, Sparki can detect the remote control signal and send the screen data several times over.

To fix this bug, we can add a simple check. We'll use a variable called lastSent that keeps track of the last time the screen data was sent. If Sparki sees the button push that triggers the data transmission, he'll first check to see if he has already sent data within the last 2 seconds. If he has, he'll cancel that request. This kind of signal filtering is called debouncing. The update to the code below should give you enough time to push the button on the remote and release it, without sending multiple copies of the screen data to the Serial connection.

#include <Sparki.h>

void setup() {
 sparki.begin();
 Serial.begin(9600);
}

//The numeric code for the "open gripper" button on the Sparki remote is "9"
//That button also looks a little bit like a "Transmit" symbol.
const int BUTTON_OPEN = 9;

void loop() {
  //Read the infrared remote. 
  //If the "open gripper"/"transmit" button is pressed, 
  //send the screen data to Processing.
  if(sparki.readIR() == BUTTON_OPEN) 
  {
    sendScreenDataToProcessing();
  }
}

void sendScreenDataToProcessing()
{
  //We use this to prevent resending the screen too quickly.
  //lastSent stores the last time the data was sent.
  //"static" variables retain their value between function calls
  static long lastSent = 0;
  if(millis() - 2000 > lastSent) //Prevent resending too quickly. Delay 2000ms (2 seconds) between transmissions.
  {
    Serial.println("<img>");   //start the image transmission by sending <img>
    for(int y=0; y<64; y++)    //loop over every row
    {
      for(int x=0; x<128; x++) //loop over every column in the current row
      {
        byte pixel = sparki.readPixel(x, y); //read the current pixel
        if(pixel == 1) //if the pixel is white / on / true / 1
        {
          Serial.print('*'); //send a *
        }
        else //if the pixel is blue / off / false / 0
        {
          Serial.print(' '); //send a blank space
        }
      }
      Serial.println(); //after every row, end the line with a return character
    }
    Serial.println("</img>"); //end the image transmission by sending </img>
    lastSent = millis(); //Update the lastSent variable with the current time.
  }
}

 


Step 6:

Processing - Getting Started

If you don't have it already, go over to the download page for Processing and get it. They have installers for Windows, Linux, and Mac OS X.

Once you have it installed, create a new sketch and let's get coding.

One thing to note is that while the coding environment for Sparki and for Processing are similar, they use different computer languages. Sparki and Arduino use C and C++, while Processing uses Java. There are a lot of similarities between the two but there are also some differences, so keep your eyes open.

One difference is that Sparki always has two function stubs waiting for you when you start a new program:

  1. void setup()
  2. void loop()

The setup() function lets you start different libraries and establish certain parameters before Sparki starts running the loop() code. Once setup() has run, Sparki runs the loop() function over and over again for as long as he is powered on.

Processing has two similar functions that are included whenever you open a new sketch:

  1. void setup()
  2. void draw()

The setup() function in Processing has the same purpose as the setup function for Sparki. The draw() function is similar to Sparki's loop() function. It runs over and over until you quit the Processing sketch. Since Processing was originally targeted to creating visual arts with the help of a computer, the draw() loop was intended to draw things on the screen over and over again. Sparki does more than just draw, so his designers chose to call his function loop() instead.

One thing about Processing sketches is that you always need to set the size of the drawing window. This should be the first thing you write inside the setup() function. We'll also set the background color of the window to match the color of Sparki's LCD.

I initially made the drawing window the same size as Sparki's LCD: 128 pixels wide by 64 pixels high. There were a few problems with that though. For one thing, the images I got were really small. For another, the images didn't look the same on the computer as on Sparki's screen. Remember how computer pixels have an aspect ratio of 1:1, but Sparki's LCD has a pixel ratio of about 1:1.5? That made the images I got from Processing end up looking squished flat compared to what Sparki's LCD looks like.

In order to make the images not look squished in Processing, I multiplied the height of the screen by 1.5, to accommodate a pixel ratio of 1:1.5. And in order to make the image large enough to see and appreciate, I multiplied both the width and the height by 4.

  • Width = 128 Sparki-pixels * 1 aspect-ratio    * 4 multiplier = 512 Processing pixels
  • Height = 64 Sparki-pixels  * 1.5 aspect-ratio * 4 multiplier = 384 Processing pixels

So here's what our Processing sketch will look like starting out:

/*
CaptureSparkiLCD.pde
Receive a screenshot of the Sparki LCD over the serial port and save it as a PNG image.
*/

void setup()
{
  //Width:  128 Sparki-pixels * 1 aspect-ratio   * 4 multiplier = 512 Processing pixels
  //Height:  64 Sparki-pixels * 1.5 aspect-ratio * 4 multiplier = 384 Processing pixels
  size(512, 384);
  background(7, 23, 197); //Red 7, Green 23, Blue 197: To match Sparki's blue LCD screen
}

void draw()
{
}

 


Step 7:

Processing - Connecting to the Serial Port

To connect Processing to the Serial port, we need to include the Serial library. We'll also define some variables to hold the port number and the Serial connection. Add this to the top of your Processing sketch:

import processing.serial.*;

Serial myPort;
short portIndex = 0; //select the com port

We'll define a function to create the serial port connection, which we'll call connectSerial(). We'll need to add a call to that function at the bottom of the setup() function. Here's what the connectSerial() function will look like:

void connectSerial()
{
  String portName = "";
  
  String[] ports = Serial.list();
  println("Available ports:");
  println("portIndex  portName");
  for(int i=0; i<ports.length; i++)
  {
    println(i + "          " + ports[i]);
  }
  println();
  print("Connecting to port index " + portIndex + ", ");
  
  try
  {
    portName = Serial.list()[portIndex];
    println(portName + " ...");
    myPort = new Serial(this, portName, 9600);
    println("Connected.");
  }
  catch(Exception e)
  {
    println("");
    println("/!\\ Could not connect.");
    println("");
    println("Note:");
    println("If the serial port can't connect, make sure that Sparkiduino's Serial Monitor is not open.");
    println("You can also change the port index by changing portIndex at the top of the Processing sketch:");
    println("short portIndex = " + portIndex + "; //select the com port");
    exit();
  }
}

That's a pretty long function. Basically what it does is try to open a Serial connection using the portIndex that is defined at the top of the sketch. If it doesn't work, it will print out some error messages that try to help you choose the right portIndex next time. You may need to change the portIndex to 1 or 2 or some other number before you can connect to your Sparki.

After that we can write some simple code to read from the Serial connection. We'll put it inside the draw() function.

void draw()
{
  if(myPort.available() > 0)
  {
    String line = myPort.readStringUntil('\n');
    print(line);
  }
}

At this point, if you have Sparki running the code that sends data over the serial connection, and Sparki's USB cable is plugged in to your computer, you should be able to run the Processing sketch and see Sparki's data scrolling in the console area at the bottom of the Processing window. Here's the full listing of the Processing code at this point:

/*
CaptureSparkiLCD.pde
Receive a screenshot of the Sparki LCD over the serial port and save it as a PNG image.
*/

import processing.serial.*;

Serial myPort;
short portIndex = 0; //select the com port

void setup()
{
  //Width:  128 Sparki-pixels * 1 aspect-ratio   * 4 multiplier = 512 Processing pixels
  //Height:  64 Sparki-pixels * 1.5 aspect-ratio * 4 multiplier = 384 Processing pixels
  size(512, 384);
  background(7, 23, 197); //Red 7, Green 23, Blue 197: To match Sparki's blue LCD screen
  connectSerial();
}

void connectSerial()
{
  String portName = "";
  
  String[] ports = Serial.list();
  println("Available ports:");
  println("portIndex  portName");
  for(int i=0; i<ports.length; i++)
  {
    println(i + "          " + ports[i]);
  }
  println();
  print("Connecting to port index " + portIndex + ", ");
  
  try
  {
    portName = Serial.list()[portIndex];
    println(portName + " ...");
    myPort = new Serial(this, portName, 9600);
    println("Connected.");
  }
  catch(Exception e)
  {
    println("");
    println("/!\\ Could not connect.");
    println("");
    println("Note:");
    println("If the serial port can't connect, make sure that Sparkiduino's Serial Monitor is not open.");
    println("You can also change the port index by changing portIndex at the top of the Processing sketch:");
    println("short portIndex = " + portIndex + "; //select the com port");
    exit();
  }
}

void draw()
{
  if(myPort.available() > 0)
  {
    String line = myPort.readStringUntil('\n');
    print(line);
  }
}

 


Step 8:

Processing - Reading and Displaying the Data

Now we'll modify the draw() function to do more with the serial data stream than just print it to the console. We want to reverse the procedure we did on the Sparki. There we read every pixel and sent ASCII characters to represent it. Now we want to read the ASCII characters and use that information to draw pixels in the Processing window.

So we'll first look for the <img> and </img> tags that start and end our image transmission. That will tell us when to start and stop reading lines to look for pixel data. Once we've found the <img> tag, we'll set a variable receivingImage to true. When we find the </img> tag, we'll set receivingImage to false.

While receivingImage is true, we'll process each line of pixel data, (representing one row on the screen) and read each character from left to right to determine if that pixel should be on or off. We also need to keep track of what line we're on (the y-coordinate) so we'll use a variable called lineIndex for that. These are declared at the top of the sketch.

When we read a pixel that should be turned, we'll draw that pixel on the screen. Remember that we enlarged our display in Processing by a multiple of 4 so that it would be easier to see. We also have the pixel aspect ratio to think about. So each Sparki pixel will actually be a rectangle 4 wide by 6 high when we draw them to the screen in Processing. In order to make that simpler to work with, I declared a pixelWidth and pixelHeight variable at the top of the sketch.

Take a look at the new Processing sketch at this point:

/*
CaptureSparkiLCD.pde
Receive a screenshot of the Sparki LCD over the serial port and save it as a PNG image.
*/

import processing.serial.*;

Serial myPort;
short portIndex = 0; //select the com port

//Pixel aspect ratio = 1:1.5 = 2:3 = 4:6
static final int pixelWidth = 4;
static final int pixelHeight = 6;

//Image status
boolean receivingImage = false;
int lineIndex = 0;

void setup()
{
  //Width:  128 Sparki-pixels * 1 aspect-ratio   * 4 multiplier = 512 Processing pixels
  //Height:  64 Sparki-pixels * 1.5 aspect-ratio * 4 multiplier = 384 Processing pixels
  size(512, 384);
  background(7, 23, 197); //Red 7, Green 23, Blue 197: To match Sparki's blue LCD screen
  connectSerial();
}

void connectSerial()
{
  String portName = "";
  
  String[] ports = Serial.list();
  println("Available ports:");
  println("portIndex  portName");
  for(int i=0; i<ports.length; i++)
  {
    println(i + "          " + ports[i]);
  }
  println();
  print("Connecting to port index " + portIndex + ", ");
  
  try
  {
    portName = Serial.list()[portIndex];
    println(portName + " ...");
    myPort = new Serial(this, portName, 9600);
    println("Connected.");
  }
  catch(Exception e)
  {
    println("");
    println("/!\\ Could not connect.");
    println("");
    println("Note:");
    println("If the serial port can't connect, make sure that Sparkiduino's Serial Monitor is not open.");
    println("You can also change the port index by changing portIndex at the top of the Processing sketch:");
    println("short portIndex = " + portIndex + "; //select the com port");
    exit();
  }
}

void draw()
{
  if(myPort.available() > 0)
  {
    String line = myPort.readStringUntil('\n'); //read one line of data from the Serial connection into a String variable
    print(line);
    if(line.trim().equals("<img>")) //If this line consists of the <img> tag that starts an image transmission...
    {
      background(7, 23, 197); //Reset the draw window to a blank blue background.
      receivingImage = true;  //Set the flag that means the next lines have image data (until we find </img>)
      lineIndex = 0;          //Set the lineIndex to 0. This represents the y-coordinate of the pixel.
    }
    else if(line.trim().equals("</img>")) //If this line consists of the </img> tag that ends an image transmission...
    {
      receivingImage = false; //Set the receivingImage flat to false - stop looking for image data lines.
    }
    else if(receivingImage)   //Otherwise, if the receivingImage flag is true...
    {
      int length = line.length(); //Find out how many characters are in this line.
      for(int x=0; x<length; x++) //Loop over every character
      {
        if(line.charAt(x) == '*') //If the character is a *, representing a white pixel...
        {
          noStroke(); //Turn off lines around boxes - just fill the exact rectangle size we specify below.
          fill(240, 240, 240); //Set the fill color to off-white. Full white would be 255, 255, 255.
          //Draw the rectangle that represents this pixel. pixelWidth and pixelHeight help represent the pixel's aspect ratio.
          rect(x * pixelWidth, lineIndex * pixelHeight, pixelWidth, pixelHeight);
        }
      }
      lineIndex++; //Increment the lineIndex, equivalent to moving the y-coordinate to the next row of the image.
    }
  }
}

Do you notice how the image that is created doesn't quite look like the image on Sparki's LCD? If you look closely at Sparki's LCD, you can see a tiny little line running between all the pixels. In order to make the Processing display look more like Sparki, I changed the drawing code to:

  1. First draw a ghosted out white rectangle the full size of a pixel.
  2. Then draw a second rectangle at full opacity, and one pixel less in width and height than the first box.

So the code that draws the rectangle changes to this:

        if(line.charAt(x) == '*')
        {
          noStroke();
          fill(240, 240, 240, 128); //Set the color to off-white, half-transparency
          rect(x * pixelWidth, lineIndex * pixelHeight, pixelWidth, pixelHeight); //Full pixel box is drawn
          fill(240, 240, 240); //Set the color to off-white, no transparency
          rect(x * pixelWidth, lineIndex * pixelHeight, pixelWidth-1, pixelHeight-1); //Draw a pixe box one pixel smaller in width and height
        }

That results are now much closer to what the Sparki LCD looks like. (See the second image above.)


Step 9:

Processing - Saving the Image to a File

We're pretty close to accomplishing our goal. Now we just need to save our image to a file.

We'll write short function called saveImage():

void saveImage()
{
  //Save an image of the current draw window. It will be in the same directory as this Processing sketch.
  saveFrame("lcd-######.png"); //save the image with a unique number represented by ######. 
}

Processing's saveFrame() function takes the name of a file and saves it. It will replace any string of #'s in the name with a unique number. That way every time you save an image of the screen, it shouldn't overwrite any previous images.

Now we need to call this function from our draw() routine. We'll add a call to this function is in the else if statement where we detect the </img> tag.

    else if(line.trim().equals("</img>")) //If this line consists of the </img> tag that ends an image transmission...
    {
      saveImage();
      receivingImage = false; //Set the receivingImage flat to false - stop looking for image data lines.
    }

That should handle any properly formatted image we receive from Sparki. Now when our sketch receives the </img> tag, it will save the draw window to a .png image file in the Processing sketch's folder.

The final Processing sketch looks like this now:

/*
CaptureSparkiLCD.pde
Receive a screenshot of the Sparki LCD over the serial port and save it as a PNG image.
*/

import processing.serial.*;

Serial myPort;
short portIndex = 0; //select the com port

//Pixel aspect ratio = 1:1.5 = 2:3 = 4:6
static final int pixelWidth = 4;
static final int pixelHeight = 6;

//Image status
boolean receivingImage = false;
int lineIndex = 0;

void setup()
{
  //Width:  128 Sparki-pixels * 1 aspect-ratio   * 4 multiplier = 512 Processing pixels
  //Height:  64 Sparki-pixels * 1.5 aspect-ratio * 4 multiplier = 384 Processing pixels
  size(512, 384);
  background(7, 23, 197); //Red 7, Green 23, Blue 197: To match Sparki's blue LCD screen
  connectSerial();
}

void connectSerial()
{
  String portName = "";
  
  String[] ports = Serial.list();
  println("Available ports:");
  println("portIndex  portName");
  for(int i=0; i<ports.length; i++)
  {
    println(i + "          " + ports[i]);
  }
  println();
  print("Connecting to port index " + portIndex + ", ");
  
  try
  {
    portName = Serial.list()[portIndex];
    println(portName + " ...");
    myPort = new Serial(this, portName, 9600);
    println("Connected.");
  }
  catch(Exception e)
  {
    println("");
    println("/!\\ Could not connect.");
    println("");
    println("Note:");
    println("If the serial port can't connect, make sure that Sparkiduino's Serial Monitor is not open.");
    println("You can also change the port index by changing portIndex at the top of the Processing sketch:");
    println("short portIndex = " + portIndex + "; //select the com port");
    exit();
  }
}

void draw()
{
  if(myPort.available() > 0)
  {
    String line = myPort.readStringUntil('\n'); //read one line of data from the Serial connection into a String variable
    print(line);
    if(line.trim().equals("<img>")) //If this line consists of the <img> tag that starts an image transmission...
    {
      background(7, 23, 197); //Reset the draw window to a blank blue background.
      receivingImage = true;  //Set the flag that means the next lines have image data (until we find </img>)
      lineIndex = 0;          //Set the lineIndex to 0. This represents the y-coordinate of the pixel.
    }
    else if(line.trim().equals("</img>")) //If this line consists of the </img> tag that ends an image transmission...
    {
      saveImage();
      receivingImage = false; //Set the receivingImage flat to false - stop looking for image data lines.
    }
    else if(receivingImage)   //Otherwise, if the receivingImage flag is true...
    {
      int length = line.length(); //Find out how many characters are in this line.
      for(int x=0; x<length; x++) //Loop over every character
      {
        if(line.charAt(x) == '*')
        {
          noStroke();
          fill(240, 240, 240, 128); //Set the color to off-white, half-transparency
          rect(x * pixelWidth, lineIndex * pixelHeight, pixelWidth, pixelHeight); //Full pixel box is drawn
          fill(240, 240, 240); //Set the color to off-white, no transparency
          rect(x * pixelWidth, lineIndex * pixelHeight, pixelWidth-1, pixelHeight-1); //Draw a pixe box one pixel smaller in width and height
        }
      }
      lineIndex++; //Increment the lineIndex, equivalent to moving the y-coordinate to the next row of the image.
    }
  }
}

void saveImage()
{
  //Save an image of the current draw window. It will be in the same directory as this Processing sketch.
  saveFrame("lcd-######.png"); //save the image with a unique number represented by ######. 
}

 


Step 10:

Enjoy Drawing with Sparki!

Now you can enjoy drawing with Sparki and save all the images and drawings you create to files on your computer that you can share with others!

You can also download the Processing sketch and some simple Sparki drawing sketches above.




Comments

Add a Comment

Sign in to comment.