339 lines
10 KiB
C++
339 lines
10 KiB
C++
|
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
|
||
|
// Copyright (C) 2018 Henner Zeller <h.zeller@acm.org>
|
||
|
//
|
||
|
// 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>
|
||
|
|
||
|
#include "pixel-mapper.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <map>
|
||
|
|
||
|
namespace rgb_matrix {
|
||
|
namespace {
|
||
|
|
||
|
class RotatePixelMapper : public PixelMapper {
|
||
|
public:
|
||
|
RotatePixelMapper() : angle_(0) {}
|
||
|
|
||
|
virtual const char *GetName() const { return "Rotate"; }
|
||
|
|
||
|
virtual bool SetParameters(int chain, int parallel, const char *param) {
|
||
|
if (param == NULL || strlen(param) == 0) {
|
||
|
angle_ = 0;
|
||
|
return true;
|
||
|
}
|
||
|
char *errpos;
|
||
|
const int angle = strtol(param, &errpos, 10);
|
||
|
if (*errpos != '\0') {
|
||
|
fprintf(stderr, "Invalid rotate parameter '%s'\n", param);
|
||
|
return false;
|
||
|
}
|
||
|
if (angle % 90 != 0) {
|
||
|
fprintf(stderr, "Rotation needs to be multiple of 90 degrees\n");
|
||
|
return false;
|
||
|
}
|
||
|
angle_ = (angle + 360) % 360;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
|
||
|
int *visible_width, int *visible_height)
|
||
|
const {
|
||
|
if (angle_ % 180 == 0) {
|
||
|
*visible_width = matrix_width;
|
||
|
*visible_height = matrix_height;
|
||
|
} else {
|
||
|
*visible_width = matrix_height;
|
||
|
*visible_height = matrix_width;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
|
||
|
int x, int y,
|
||
|
int *matrix_x, int *matrix_y) const {
|
||
|
switch (angle_) {
|
||
|
case 0:
|
||
|
*matrix_x = x;
|
||
|
*matrix_y = y;
|
||
|
break;
|
||
|
case 90:
|
||
|
*matrix_x = matrix_width - y - 1;
|
||
|
*matrix_y = x;
|
||
|
break;
|
||
|
case 180:
|
||
|
*matrix_x = matrix_width - x - 1;
|
||
|
*matrix_y = matrix_height - y - 1;
|
||
|
break;
|
||
|
case 270:
|
||
|
*matrix_x = y;
|
||
|
*matrix_y = matrix_height - x - 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int angle_;
|
||
|
};
|
||
|
|
||
|
class MirrorPixelMapper : public PixelMapper {
|
||
|
public:
|
||
|
MirrorPixelMapper() : horizontal_(true) {}
|
||
|
|
||
|
virtual const char *GetName() const { return "Mirror"; }
|
||
|
|
||
|
virtual bool SetParameters(int chain, int parallel, const char *param) {
|
||
|
if (param == NULL || strlen(param) == 0) {
|
||
|
horizontal_ = true;
|
||
|
return true;
|
||
|
}
|
||
|
if (strlen(param) != 1) {
|
||
|
fprintf(stderr, "Mirror parameter should be a single "
|
||
|
"character:'V' or 'H'\n");
|
||
|
}
|
||
|
switch (*param) {
|
||
|
case 'V':
|
||
|
case 'v':
|
||
|
horizontal_ = false;
|
||
|
break;
|
||
|
case 'H':
|
||
|
case 'h':
|
||
|
horizontal_ = true;
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "Mirror parameter should be either 'V' or 'H'\n");
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
|
||
|
int *visible_width, int *visible_height)
|
||
|
const {
|
||
|
*visible_height = matrix_height;
|
||
|
*visible_width = matrix_width;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
|
||
|
int x, int y,
|
||
|
int *matrix_x, int *matrix_y) const {
|
||
|
if (horizontal_) {
|
||
|
*matrix_x = matrix_width - 1 - x;
|
||
|
*matrix_y = y;
|
||
|
} else {
|
||
|
*matrix_x = x;
|
||
|
*matrix_y = matrix_height - 1 - y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
bool horizontal_;
|
||
|
};
|
||
|
|
||
|
// If we take a long chain of panels and arrange them in a U-shape, so
|
||
|
// that after half the panels we bend around and continue below. This way
|
||
|
// we have a panel that has double the height but only uses one chain.
|
||
|
// A single chain display with four 32x32 panels can then be arranged in this
|
||
|
// 64x64 display:
|
||
|
// [<][<][<][<] }- Raspbery Pi connector
|
||
|
//
|
||
|
// can be arranged in this U-shape
|
||
|
// [<][<] }----- Raspberry Pi connector
|
||
|
// [>][>]
|
||
|
//
|
||
|
// This works for more than one chain as well. Here an arrangement with
|
||
|
// two chains with 8 panels each
|
||
|
// [<][<][<][<] }-- Pi connector #1
|
||
|
// [>][>][>][>]
|
||
|
// [<][<][<][<] }--- Pi connector #2
|
||
|
// [>][>][>][>]
|
||
|
class UArrangementMapper : public PixelMapper {
|
||
|
public:
|
||
|
UArrangementMapper() : parallel_(1) {}
|
||
|
|
||
|
virtual const char *GetName() const { return "U-mapper"; }
|
||
|
|
||
|
virtual bool SetParameters(int chain, int parallel, const char *param) {
|
||
|
if (chain < 2) { // technically, a chain of 2 would work, but somewhat pointless
|
||
|
fprintf(stderr, "U-mapper: need at least --led-chain=4 for useful folding\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (chain % 2 != 0) {
|
||
|
fprintf(stderr, "U-mapper: Chain (--led-chain) needs to be divisible by two\n");
|
||
|
return false;
|
||
|
}
|
||
|
parallel_ = parallel;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
|
||
|
int *visible_width, int *visible_height)
|
||
|
const {
|
||
|
*visible_width = (matrix_width / 64) * 32; // Div at 32px boundary
|
||
|
*visible_height = 2 * matrix_height;
|
||
|
if (matrix_height % parallel_ != 0) {
|
||
|
fprintf(stderr, "%s For parallel=%d we would expect the height=%d "
|
||
|
"to be divisible by %d ??\n",
|
||
|
GetName(), parallel_, matrix_height, parallel_);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
|
||
|
int x, int y,
|
||
|
int *matrix_x, int *matrix_y) const {
|
||
|
const int panel_height = matrix_height / parallel_;
|
||
|
const int visible_width = (matrix_width / 64) * 32;
|
||
|
const int slab_height = 2 * panel_height; // one folded u-shape
|
||
|
const int base_y = (y / slab_height) * panel_height;
|
||
|
y %= slab_height;
|
||
|
if (y < panel_height) {
|
||
|
x += matrix_width / 2;
|
||
|
} else {
|
||
|
x = visible_width - x - 1;
|
||
|
y = slab_height - y - 1;
|
||
|
}
|
||
|
*matrix_x = x;
|
||
|
*matrix_y = base_y + y;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int parallel_;
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
class VerticalMapper : public PixelMapper {
|
||
|
public:
|
||
|
VerticalMapper() {}
|
||
|
|
||
|
virtual const char *GetName() const { return "V-mapper"; }
|
||
|
|
||
|
virtual bool SetParameters(int chain, int parallel, const char *param) {
|
||
|
chain_ = chain;
|
||
|
parallel_ = parallel;
|
||
|
// optional argument :Z allow for every other panel to be flipped
|
||
|
// upside down so that cabling can be shorter:
|
||
|
// [ O < I ] without Z [ O < I ]
|
||
|
// ,---^ <---- ^
|
||
|
// [ O < I ] [ I > O ]
|
||
|
// ,---^ with Z ^
|
||
|
// [ O < I ] ---> [ O < I ]
|
||
|
z_ = (param && strcasecmp(param, "Z") == 0);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual bool GetSizeMapping(int matrix_width, int matrix_height,
|
||
|
int *visible_width, int *visible_height)
|
||
|
const {
|
||
|
*visible_width = matrix_width * parallel_ / chain_;
|
||
|
*visible_height = matrix_height * chain_ / parallel_;
|
||
|
#if 0
|
||
|
fprintf(stderr, "%s: C:%d P:%d. Turning W:%d H:%d Physical "
|
||
|
"into W:%d H:%d Virtual\n",
|
||
|
GetName(), chain_, parallel_,
|
||
|
*visible_width, *visible_height, matrix_width, matrix_height);
|
||
|
#endif
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
|
||
|
int x, int y,
|
||
|
int *matrix_x, int *matrix_y) const {
|
||
|
const int panel_width = matrix_width / chain_;
|
||
|
const int panel_height = matrix_height / parallel_;
|
||
|
const int x_panel_start = y / panel_height * panel_width;
|
||
|
const int y_panel_start = x / panel_width * panel_height;
|
||
|
const int x_within_panel = x % panel_width;
|
||
|
const int y_within_panel = y % panel_height;
|
||
|
const bool needs_flipping = z_ && (y / panel_height) % 2 == 1;
|
||
|
*matrix_x = x_panel_start + (needs_flipping
|
||
|
? panel_width - 1 - x_within_panel
|
||
|
: x_within_panel);
|
||
|
*matrix_y = y_panel_start + (needs_flipping
|
||
|
? panel_height - 1 - y_within_panel
|
||
|
: y_within_panel);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
bool z_;
|
||
|
int chain_;
|
||
|
int parallel_;
|
||
|
};
|
||
|
|
||
|
|
||
|
typedef std::map<std::string, PixelMapper*> MapperByName;
|
||
|
static void RegisterPixelMapperInternal(MapperByName *registry,
|
||
|
PixelMapper *mapper) {
|
||
|
assert(mapper != NULL);
|
||
|
std::string lower_name;
|
||
|
for (const char *n = mapper->GetName(); *n; n++)
|
||
|
lower_name.append(1, tolower(*n));
|
||
|
(*registry)[lower_name] = mapper;
|
||
|
}
|
||
|
|
||
|
static MapperByName *CreateMapperMap() {
|
||
|
MapperByName *result = new MapperByName();
|
||
|
|
||
|
// Register all the default PixelMappers here.
|
||
|
RegisterPixelMapperInternal(result, new RotatePixelMapper());
|
||
|
RegisterPixelMapperInternal(result, new UArrangementMapper());
|
||
|
RegisterPixelMapperInternal(result, new VerticalMapper());
|
||
|
RegisterPixelMapperInternal(result, new MirrorPixelMapper());
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static MapperByName *GetMapperMap() {
|
||
|
static MapperByName *singleton_instance = CreateMapperMap();
|
||
|
return singleton_instance;
|
||
|
}
|
||
|
} // anonymous namespace
|
||
|
|
||
|
// Public API.
|
||
|
void RegisterPixelMapper(PixelMapper *mapper) {
|
||
|
RegisterPixelMapperInternal(GetMapperMap(), mapper);
|
||
|
}
|
||
|
|
||
|
std::vector<std::string> GetAvailablePixelMappers() {
|
||
|
std::vector<std::string> result;
|
||
|
MapperByName *m = GetMapperMap();
|
||
|
for (MapperByName::const_iterator it = m->begin(); it != m->end(); ++it) {
|
||
|
result.push_back(it->second->GetName());
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
const PixelMapper *FindPixelMapper(const char *name,
|
||
|
int chain, int parallel,
|
||
|
const char *parameter) {
|
||
|
std::string lower_name;
|
||
|
for (const char *n = name; *n; n++) lower_name.append(1, tolower(*n));
|
||
|
MapperByName::const_iterator found = GetMapperMap()->find(lower_name);
|
||
|
if (found == GetMapperMap()->end()) {
|
||
|
fprintf(stderr, "%s: no such mapper\n", name);
|
||
|
return NULL;
|
||
|
}
|
||
|
PixelMapper *mapper = found->second;
|
||
|
if (mapper == NULL) return NULL; // should not happen.
|
||
|
if (!mapper->SetParameters(chain, parallel, parameter))
|
||
|
return NULL; // Got parameter, but couldn't deal with it.
|
||
|
return mapper;
|
||
|
}
|
||
|
} // namespace rgb_matrix
|