Skip to content

Commit

Permalink
o Provide simple universal image loader, including animated GIFs.
Browse files Browse the repository at this point in the history
  Addresses issue hzeller#11
  • Loading branch information
hzeller committed Mar 3, 2015
1 parent 7785d65 commit 7ddfa25
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 6 deletions.
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CXXFLAGS=-Wall -O3 -g
OBJECTS=demo-main.o minimal-example.o text-example.o
OBJECTS=demo-main.o minimal-example.o text-example.o led-image-viewer.o
BINARIES=led-matrix minimal-example text-example
ALL_BINARIES="$(BINARIES) led-image-viewer"

# Where our library resides. It is split between includes and the binary
# library in lib
Expand All @@ -10,6 +11,10 @@ RGB_LIBRARY_NAME=rgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
LDFLAGS+=-L$(RGB_LIBDIR) -l$(RGB_LIBRARY_NAME) -lrt -lm -lpthread

# Imagemagic flags, only needed if actually compiled.
MAGICK_CXXFLAGS=`Magick++-config --cppflags --cxxflags`
MAGICK_LDFLAGS=`Magick++-config --ldflags --libs`

all : $(BINARIES)

$(RGB_LIBRARY):
Expand All @@ -24,9 +29,15 @@ minimal-example : minimal-example.o $(RGB_LIBRARY)
text-example : text-example.o $(RGB_LIBRARY)
$(CXX) $(CXXFLAGS) text-example.o -o $@ $(LDFLAGS)

led-image-viewer: led-image-viewer.o $(RGB_LIBRARY)
$(CXX) $(CXXFLAGS) led-image-viewer.o -o $@ $(LDFLAGS) $(MAGICK_LDFLAGS)

%.o : %.cc
$(CXX) -I$(RGB_INCDIR) $(CXXFLAGS) -c -o $@ $<

led-image-viewer.o : led-image-viewer.cc
$(CXX) -I$(RGB_INCDIR) $(CXXFLAGS) $(MAGICK_CXXFLAGS) -c -o $@ $<

clean:
rm -f $(OBJECTS) $(BINARIES)
rm -f $(OBJECTS) $(ALL_BINARIES)
$(MAKE) -C lib clean
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ For multiple parallel boards to work, you want to uncomment
in [lib/Makefile](./lib/Makefile). While two parallel panels will work out
of the box without this, it frees up the pins for I²C, which you might
want to use for other things. However, for three panels to work, you definitely
want to uncomment this.
need to uncomment this.

The second and third panel chain share some of the wires of the first panel:
connect **GND, A, B, C, D, OE, CLK** and **STR** to the same pins you already
Expand Down Expand Up @@ -191,6 +191,22 @@ clears the screen (if you want to display an empty line, just send a space).
![Time][time]


### Image Viewer ###

One of the possibly useful demo applications is is also image viewer that
reads all kinds of image formats, including animated gifs. It is not compiled
by default, as you need to install the imagemagick depdendency

sudo aptitude install libmagick++-dev
make led-image-viewer

Then, you can run it

sudo ./led-image-viewer myimage.gif

It also supports the standard options to specify the connected
displays (`-r`, `-c`, `-P`).

**CPU use**

These displays need to be updated constantly to show an image with PWMed
Expand Down
237 changes: 237 additions & 0 deletions led-image-viewer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Copyright (C) 2015 Henner Zeller <[email protected]>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>

// To use this image viewer, first get image-magick development files
// $ sudo aptitude install libmagick++-dev
//
// Then compile with
// $ make led-image-viewer

#include "led-matrix.h"

#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <vector>
#include <Magick++.h>
#include <magick/quantum.h>

using rgb_matrix::GPIO;
using rgb_matrix::RGBMatrix;

volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}

namespace {
class PreprocessedFrame {
public:
PreprocessedFrame(const Magick::Image &img)
: width_(img.columns()), height_(img.rows()),
content_(new Pixel[width_ * height_]) {
int delay_time = img.animationDelay(); // in 1/100s of a second.
if (delay_time < 1) delay_time = 1;
delay_micros_ = delay_time * 10000;

for (int y = 0; y < height_; ++y) {
for (int x = 0; x < width_; ++x) {
const Magick::Color &c = img.pixelColor(x, y);
content_[y * width_ + x] =
Pixel(MagickCore::ScaleQuantumToChar(c.redQuantum()),
MagickCore::ScaleQuantumToChar(c.greenQuantum()),
MagickCore::ScaleQuantumToChar(c.blueQuantum()));
}
}
}

~PreprocessedFrame() { delete [] content_; }

void CopyToCanvas(rgb_matrix::Canvas *canvas) const {
Pixel *pixel = content_;
for (int y = 0; y < height_; ++y) {
for (int x = 0; x < width_; ++x) {
canvas->SetPixel(x, y, pixel->r, pixel->g, pixel->b);
++pixel;
}
}
}

int delay_micros() const {
return delay_micros_;
}

private:
struct Pixel {
Pixel() : r(0), g(0), b(0){}
Pixel(uint8_t rr, uint8_t gg, uint8_t bb) : r(rr), g(gg), b(bb){}
uint8_t r; uint8_t g; uint8_t b;
};
const int width_;
const int height_;
int delay_micros_;

Pixel *content_;
};
}

// Load still image or animation.
static bool LoadAnimation(const char *filename, int width, int height,
std::vector<PreprocessedFrame*> *sequence_pics) {
std::vector<Magick::Image> frames;
fprintf(stderr, "Read image...\n");
readImages(&frames, filename);
if (frames.size() == 0) {
fprintf(stderr, "No image found.");
return false;
}

// Put together the animation from single frames. GIFs can have nasty
// disposal modes, but they are handled nicely by coalesceImages()
std::vector<Magick::Image> coalesced;
if (frames.size() > 1) {
fprintf(stderr, "Assembling animation with %d frames.\n",
(int)frames.size());
Magick::coalesceImages(&coalesced, frames.begin(), frames.end());
} else {
coalesced.push_back(frames[0]); // just a single still image.
}

fprintf(stderr, "Scale ... %dx%d -> %dx%d\n",
(int)coalesced[0].columns(), (int)coalesced[0].rows(),
width, height);
for (size_t i = 0; i < coalesced.size(); ++i) {
coalesced[i].zoom(Magick::Geometry(width, height));
}
fprintf(stderr, "Preprocess for display.\n");
for (size_t i = 0; i < coalesced.size(); ++i) {
sequence_pics->push_back(new PreprocessedFrame(coalesced[i]));
}
return true;
}

static void DisplayAnimation(const std::vector<PreprocessedFrame*> &frames,
rgb_matrix::Canvas *canvas) {
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
fprintf(stderr, "Display.\n");
for (unsigned int i = 0; !interrupt_received; ++i) {
const PreprocessedFrame *frame = frames[i % frames.size()];
frame->CopyToCanvas(canvas);
if (frames.size() == 1) {
sleep(86400); // Only one image. Nothing to do.
} else {
usleep(frame->delay_micros());
}
}
}

static int usage(const char *progname) {
fprintf(stderr, "usage: %s [options] <image>\n", progname);
fprintf(stderr, "Options:\n"
"\t-r <rows> : Display rows. 16 for 16x32, 32 for 32x32. "
"Default: 32\n"
"\t-P <parallel> : For Plus-models or RPi2: parallel chains. 1..3. "
"Default: 1\n"
"\t-c <chained> : Daisy-chained boards. Default: 1.\n"
"\t-d : Run as daemon.\n");
return 1;
}

int main(int argc, char *argv[]) {
int rows = 32;
int chain = 1;
int parallel = 1;
int pwm_bits = -1;
bool as_daemon = false;

int opt;
while ((opt = getopt(argc, argv, "r:P:c:p:")) != -1) {
switch (opt) {
case 'r': rows = atoi(optarg); break;
case 'P': parallel = atoi(optarg); break;
case 'c': chain = atoi(optarg); break;
case 'p': pwm_bits = atoi(optarg); break;
case 'd': as_daemon = true; break;
default:
return usage(argv[0]);
}
}

if (rows != 16 && rows != 32) {
fprintf(stderr, "Rows can either be 16 or 32\n");
return usage(argv[0]);
}

if (chain < 1) {
fprintf(stderr, "Chain outside usable range\n");
return usage(argv[0]);
}
if (chain > 8) {
fprintf(stderr, "That is a long chain. Expect some flicker.\n");
}
if (parallel < 1 || parallel > 3) {
fprintf(stderr, "Parallel outside usable range.\n");
return usage(argv[0]);
}

if (optind >= argc) {
fprintf(stderr, "Expected image filename.\n");
return usage(argv[0]);
}

const char *filename = argv[optind];

/*
* Set up GPIO pins. This fails when not running as root.
*/
GPIO io;
if (!io.Init())
return 1;

// Start daemon before we start any threads.
if (as_daemon) {
if (fork() != 0)
return 0;
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}

RGBMatrix *const matrix = new RGBMatrix(&io, rows, chain, parallel);
if (pwm_bits >= 0 && !matrix->SetPWMBits(pwm_bits)) {
fprintf(stderr, "Invalid range of pwm-bits\n");
return 1;
}

std::vector<PreprocessedFrame*> sequence_pics;
if (!LoadAnimation(filename, matrix->width(), matrix->height(),
&sequence_pics)) {
return 0;
}

DisplayAnimation(sequence_pics, matrix);

fprintf(stderr, "Caught signal. Exiting.\n");

// Animation finished. Shut down the RGB matrix.
matrix->Clear();
delete matrix;

return 0;
}
2 changes: 1 addition & 1 deletion lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ TARGET=librgbmatrix.a
# Hence we leave this off for now for the least amount of surprise.
# If you connect 3 chains in parallel, switch this on. The default
# will work up to 2 chains just fine, but you won't be able to use I²C.
DEFINES+=-DSUPPORT_MULTI_PARALLEL
#DEFINES+=-DSUPPORT_MULTI_PARALLEL

# If you see that your display is inverse, you might have a matrix variant
# has uses inverse logic for the RGB bits. In that case: uncomment this.
Expand Down
2 changes: 1 addition & 1 deletion minimal-example.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) {
*/
int rows = 32; // A 32x32 display. Use 16 when this is a 16x32 display.
int chain = 1; // Number of boards chained together.
int parallel = 1; // Number of chains in parallel (1 or 2). 2 for plus or Pi2
int parallel = 1; // Number of chains in parallel (1..3). > 1 for plus or Pi2
Canvas *canvas = new RGBMatrix(&io, rows, chain, parallel);

DrawOnCanvas(canvas); // Using the canvas.
Expand Down
4 changes: 3 additions & 1 deletion text-example.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static int usage(const char *progname) {
"\t-f <font-file>: Use given font.\n"
"\t-r <rows> : Display rows. 16 for 16x32, 32 for 32x32. "
"Default: 32\n"
"\t-P <parallel> : For Plus-models or RPi2: parallel chains. 1 or 2. "
"\t-P <parallel> : For Plus-models or RPi2: parallel chains. 1..3. "
"Default: 1\n"
"\t-c <chained> : Daisy-chained boards. Default: 1.\n"
"\t-x <x-origin> : X-Origin of displaying text (Default: 0)\n"
Expand Down Expand Up @@ -60,6 +60,8 @@ int main(int argc, char *argv[]) {
return usage(argv[0]);
}
break;
default:
return usage(argv[0]);
}
}

Expand Down

0 comments on commit 7ddfa25

Please sign in to comment.