Off-The-Shelf Hacker: Give Your On-Screen Gauge a New Face

Sometimes inspiration is like a flash of lightning, out of the blue.
That was the case this past week as I pondered the aesthetics of the on-screen gauge I made for my last project, the Steampunk Conference Badge. You may recall the rather crude dial and pointer I built which took input from the ultrasonic range finder and displayed the distance to an object on the gauge:

Early on-screen gauge using Processing code
It’s basic and plain. Not only that, the look of that gauge doesn’t really fit the theme of the Steampunk conference badge. We can do better and people appreciate that extra little attention to detail.
Today we’ll look at how to put any dial you’d like on your on-screen gauges. The technique works on a wearable LCD, as well as, a full-sized monitor.
Grab a Dial Face
Instead of writing code that manually locates text and tick marks around the gauge, I decided it would be much easier to just display a picture of a dial and code only for the rotating pointer. Development was carried out on my “5th generation” conference badge, running a Raspberry Pi 2, the Processing 3.0 programming language and a 19-inch monitor hooked up to the HDMI output. I also used the Processing IDE for the Processing programming language, when viewing the code on the HDMI monitor.
I can hear readers now, “Gee Doc, using a picture for the gauge is a pretty obvious DUH!” All I can say is that enlightenment comes in many forms, my friend and I’m thankful that the old incandescent Edison bulb still lights up occasionally.
If it were easy, a fifth grader could do it.
Processing has several functions, called “PImage,” “loadImage,” and “image” that are specifically designed to put pictures on the screen. It works with GIF, JPEG and PNG files. Here’s a code snippet right out of the processing.org reference manual.
1 2 3 4 5 6 7 8 9 10 |
PImage photo; void setup() { size(100, 100); photo = loadImage("laDefense.jpg"); } void draw() { image(photo, 0, 0); } |
The “size” line establishes the size of the viewing window, on the screen. PImage establishes a suitable variable. The “loadImage” function then points to the file you want to display using the “loadImage” command.
Finally, you then execute the “image” function, using the photo variable along with the x and y coordinates, where you’d like the picture located. The upper-left corner is 0,0 by default.
There are about a million cool steampunk gauges on the Internet. Punch “steampunk gauge face” into Google, hit the “images” tab and you’ll find them. Be sure to honor the copyrights.
If you are handy with your cell phone camera, you could also snap a picture of a real-life gauge and incorporate that into your display. Fill as much of the frame as you can, with the gauge and try to hold the camera parallel to the gauge face, so there isn’t any distortion in the image.
Once you have an appropriate gauge picture use your favorite graphics editor to set it up to work with your code. Here’s a simple gauge I made with Inkscape and the GIMP. Notice the coffee stain? That’s a filter you can apply in the GIMP.

Gauge made with Inkscape and the GIMP
When you’re happy with the gauge graphics, make sure to crop the photo to only show the face of the gauge. Then, scale the image to match the window size, in the Processing code. The gauge will display nicely on the badge at 300 x 300 pixels.
Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
/* * Steampunk Gauge */ import processing.net.*; PFont font; PImage photo; float x, y; String angle = "0"; // angle in degrees String ivalue = "0"; // input value float gaugeaddangle = 275.0; // gauge additional angle to max value float gaugestartangle = 135.0; // gauge angle from 3:00 zero position float maxinputvalue = 157.5; // max limit of measurement float anglef = 0.0; float segLength = 127; // pointer length from center to max point BufferedReader reader; void setup() { size(300, 300); // window size, works with existing pointer, labels photo = loadImage("/home/pi/hand-drawn-gauge2.png"); strokeWeight(3); // line weight stroke(0,0,0); // line color (RGB) x = width * 0.5; // needle x origin in middle of window y = height * 0.5; // needle y origin in middle of window reader = createReader("/dev/ttyAMA0"); // if no gauge, execute stty command in shell first } void draw() { background(255); // make background white (255) image(photo,0,0); try { ivalue = reader.readLine(); } catch (IOException e) { e.printStackTrace(); ivalue = null; } if (ivalue == null) { // Stop reading because of an error or file is empty noLoop(); } if (ivalue != null) { print(ivalue); print("\n"); // anglef = radians(135) + radians(270); anglef = (radians(gaugestartangle) + (float(ivalue) * radians(gaugeaddangle/maxinputvalue))); } pushMatrix(); segment(x, y, anglef); popMatrix(); } void segment(float x, float y, float a) { translate(x, y); rotate(a); line(0, 0, segLength, 0); line(segLength-15, 7, segLength, 0); line(segLength-15, -7, segLength, 0); line(0, 0, -50, 0); line(-30, 0, -40, -10); line(-30, 0, -40, 10); line(-40, 0, -50, -10); line(-40, 0, -50, 10); line(-50, 0, -60, -10); line(-50, 0, -60, 10); } |
Notice that I used the entire pathname of the file in the “photo” function. The graphics editing was completed on my Linux notebook and I used rcp to copy the files over to the Pi 2, on the conference badge. You can store your dial files anywhere, it was just easier to copy them to the pi user’s home directory.
You might also want to hand-draw a dial face or have an artist mock-up a really fancy gauge. The process is the same. Take a good pic with the phone, send it to your notebook, edit the graphics, crop to the gauge outline, scale the image to match the Processing code window size, export the graphics to a .png file and ship it over to the Pi as a graphics file. I typically use .png graphic files for most everything, including what I put up on Off-The-Shelf Hacker.

Hand-drawn gauge face
If you take a look at the code you’ll also see how I convert distance values, from the Arduino/ultrasonic sensor USB feed to angles for the gauge pointer. Toward the bottom, in the segment function, you’ll see how the fletching is built for the short end of the pointer. That’s all those line statements. There’s probably a way to rework that code so the whole pointer object could be scaled by just using a pointer length variable.
Be aware that Processing specifies angles in radians. I used the radians() function to convert my start and add angles to radian measure. Also, the input is simply a text stream having values from 0 to 157 inches, which corresponds to the 0- to the 400-centimeter range of the ultrasonic sensor.
Pin Those Needles
Putting a cool face on our on-screen gauges isn’t very hard, although there are a lot of steps. Once you do it a couple of times, the workflow will be straightforward and you can try all kinds of interesting variations.
Taking the on-screen gauge concept a little further, I think it would be a lot of fun to make the pointer spin wildly around the gauge, a couple of times when first bringing up the gauge. This would add drama to the start-up procedure. After the initial spin, the gauge pointer could settle down and track input values, as they are received. That could easily be done with code. Exaggeration and over-the-top behavior is part of the fun of steampunk.
Add those little extra details to your projects, when you can. It will set your work apart from the average.
Feature image via Pixabay.