Skip to content

Instantly share code, notes, and snippets.

@buyoh
Created December 21, 2023 13:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save buyoh/3f786fe400f42c881cdc84b1153f4d00 to your computer and use it in GitHub Desktop.
Save buyoh/3f786fe400f42c881cdc84b1153f4d00 to your computer and use it in GitHub Desktop.
#クリスマスプロ生ちゃん もみの木をdispmanxだけで描画する https://twitter.com/shonen9th/status/1737826399497666618
// ## Build
// g++ --std=c++17 tree2.cpp -o tree2 -lbcm_host
//
// ## Remarks
// DRM VC4 V3D driver needs to be disabled.
// Open `/boot/config.txt` and commented out `dtoverlay=vc4-fkms-v3d`.
#include "bcm_host.h"
#include <cassert>
#include <cmath>
#include <iostream>
#include <memory>
#include <vector>
template <typename T> constexpr T RGB565(T r, T g, T b) {
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}
template <typename T> constexpr T AlignUp(T value, T alignment) {
return (value + alignment - 1) & ~(alignment - 1);
}
// ----------------------------------------------------------------------------
class DmxDisplay {
public:
DmxDisplay() {
bcm_host_init();
display_ = vc_dispmanx_display_open(DISPMANX_ID_HDMI0);
assert(display_ != 0);
int ret = vc_dispmanx_display_get_info(display_, &info_);
assert(ret == 0);
}
~DmxDisplay() { vc_dispmanx_display_close(display_); }
DISPMANX_DISPLAY_HANDLE_T handle() const { return display_; }
int width() const { return info_.width; }
int height() const { return info_.height; }
private:
DISPMANX_DISPLAY_HANDLE_T display_;
DISPMANX_MODEINFO_T info_;
};
class DmxRect {
public:
DmxRect(int x, int y, int width, int height) : rect_{x, y, width, height} {}
const VC_RECT_T *rect() const { return &rect_; }
private:
VC_RECT_T rect_;
};
class DmxResource {
public:
DmxResource(int width, int height)
: width_(width), height_(height), pitch_(AlignUp(width * 2, 32)) {
resource_ = vc_dispmanx_resource_create(VC_IMAGE_RGB565, width, height,
&vc_image_ptr_);
assert(resource_ != 0);
}
~DmxResource() { vc_dispmanx_resource_delete(resource_); }
DISPMANX_RESOURCE_HANDLE_T handle() const { return resource_; }
int width() const { return width_; }
int height() const { return height_; }
int pitch() const { return pitch_; }
void write(const uint8_t *data) {
VC_RECT_T rect = {0, 0, width_, height_};
int ret = vc_dispmanx_resource_write_data(
resource_, VC_IMAGE_RGB565, pitch_,
static_cast<void *>(const_cast<uint8_t *>(data)), &rect);
assert(ret == 0);
}
private:
int width_, height_;
int pitch_;
int row_length_;
DISPMANX_RESOURCE_HANDLE_T resource_;
uint32_t vc_image_ptr_;
};
class DmxUpdate {
public:
DmxUpdate() {}
~DmxUpdate() {}
void Start(int priority) {
if (update_ != 0) {
Sync();
}
update_ = vc_dispmanx_update_start(priority);
assert(update_ != 0);
}
void Sync() {
int ret = vc_dispmanx_update_submit_sync(update_);
if (ret != 0) {
std::cout << "vc_dispmanx_update_submit_sync failed: " << ret
<< std::endl;
}
assert(ret == 0);
update_ = 0;
}
DISPMANX_UPDATE_HANDLE_T handle() const { return update_; }
private:
DISPMANX_UPDATE_HANDLE_T update_ = 0;
};
class DmxElement {
public:
static DmxElement AddTo(const DmxDisplay &display, const DmxUpdate &update,
int layer, const DmxRect &src_rect,
const DmxRect &dst_rect,
DISPMANX_RESOURCE_HANDLE_T resource) {
// よくわからん
static VC_DISPMANX_ALPHA_T alpha = VC_DISPMANX_ALPHA_T{
(DISPMANX_FLAGS_ALPHA_T)(DISPMANX_FLAGS_ALPHA_FROM_SOURCE |
DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS),
120, /*alpha 0->255*/
0 /* mask */};
DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(
update.handle(), display.handle(), layer, dst_rect.rect(), resource,
src_rect.rect(), DISPMANX_PROTECTION_NONE, &alpha, nullptr,
DISPMANX_NO_ROTATE);
assert(element != 0);
return DmxElement(element);
}
void Remove(const DmxUpdate &update) {
int ret = vc_dispmanx_element_remove(update.handle(), element_);
assert(ret == 0);
}
private:
explicit DmxElement(DISPMANX_ELEMENT_HANDLE_T element) : element_(element) {}
DISPMANX_ELEMENT_HANDLE_T element_;
};
// ----------------------------------------------------------------------------
struct ShapePoint {
ShapePoint() : x(0), y(0) {}
ShapePoint(int x, int y) : x(x), y(y) {}
int x, y;
};
class Shape {
public:
virtual ~Shape() {}
virtual std::pair<bool, uint16_t> Get(ShapePoint point) const = 0;
};
class ShapePolygon : public Shape {
public:
ShapePolygon(const std::vector<ShapePoint> &points, uint16_t color)
: points_(points), color_(color) {}
std::pair<bool, uint16_t> Get(ShapePoint point) const {
bool c = false;
for (int i = 0, j = (int)points_.size() - 1; i < (int)points_.size();
j = i++) {
if (((points_[i].y > point.y) != (points_[j].y > point.y)) &&
(point.x < (points_[j].x - points_[i].x) * (point.y - points_[i].y) /
(points_[j].y - points_[i].y) +
points_[i].x))
c = !c;
}
return c ? std::make_pair(true, color_)
: std::make_pair(false, uint16_t(0));
}
private:
std::vector<ShapePoint> points_;
uint16_t color_;
};
class ShapeCircle : public Shape {
public:
ShapeCircle(int x, int y, int radius, uint16_t color)
: x_(x), y_(y), radius_(radius), color_(color) {}
std::pair<bool, uint16_t> Get(ShapePoint point) const {
int dx = point.x - x_;
int dy = point.y - y_;
return dx * dx + dy * dy <= radius_ * radius_
? std::make_pair(true, color_)
: std::make_pair(false, uint16_t(0));
}
private:
int x_, y_, radius_;
uint16_t color_;
};
class ShapeGroup : public Shape {
public:
ShapeGroup(std::vector<std::unique_ptr<Shape>> &&shapes)
: shapes_(std::move(shapes)) {}
std::pair<bool, uint16_t> Get(ShapePoint point) const {
for (const auto &shape : shapes_) {
auto c = shape->Get(point);
if (c.first) {
return c;
}
}
return std::make_pair(false, uint16_t(0));
}
private:
// int x_, y_, width_, height_;
std::vector<std::unique_ptr<Shape>> shapes_;
};
class ShapeCanvasRGB565 {
public:
ShapeCanvasRGB565(int width, int height, int pitch)
: width_(width), height_(height), pitch_(pitch) {
data_.resize(pitch_ * height_);
}
void UpdateAll(uint16_t background_color,
const std::vector<std::unique_ptr<Shape>> &shapes) {
for (int y = 0; y < height_; ++y) {
uint16_t *row = reinterpret_cast<uint16_t *>(&data_[y * pitch_]);
for (int x = 0; x < width_; ++x) {
row[x] = background_color;
for (const auto &shape : shapes) {
auto c = shape->Get(ShapePoint(x, y));
if (c.first) {
row[x] = c.second;
break;
}
}
}
}
}
const std::vector<uint8_t> &data() const { return data_; }
private:
int width_, height_, pitch_;
std::vector<uint8_t> data_;
};
// ----------------------------------------------------------------------------
std::unique_ptr<Shape> CreateShapeStar(int x, int y, int radius,
uint16_t color) {
std::vector<ShapePoint> pp;
for (int i = 0; i < 5; ++i) {
pp.emplace_back(x + radius * cos(2 * M_PI * i / 5),
y + radius * sin(2 * M_PI * i / 5));
pp.emplace_back(x + radius / 2 * cos(2 * M_PI * (i + 0.5) / 5),
y + radius / 2 * sin(2 * M_PI * (i + 0.5) / 5));
}
return std::make_unique<ShapePolygon>(std::move(pp), color);
}
std::unique_ptr<Shape> CreateShapeTree(int x, int y, int width, int height) {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.emplace_back(std::make_unique<ShapePolygon>(
std::vector<ShapePoint>{ShapePoint{x, y + height * 2 / 5},
ShapePoint{x + width / 2, y},
ShapePoint{x + width, y + height * 2 / 5}},
RGB565(0x20, 0xc0, 0x00)));
shapes.emplace_back(std::make_unique<ShapePolygon>(
std::vector<ShapePoint>{ShapePoint{x, y + height * 3 / 5},
ShapePoint{x + width / 2, y + height / 5},
ShapePoint{x + width, y + height * 3 / 5}},
RGB565(0x20, 0xc0, 0x00)));
shapes.emplace_back(std::make_unique<ShapePolygon>(
std::vector<ShapePoint>{ShapePoint{x, y + height * 4 / 5},
ShapePoint{x + width / 2, y + height * 2 / 5},
ShapePoint{x + width, y + height * 4 / 5}},
RGB565(0x20, 0xc0, 0x00)));
shapes.emplace_back(std::make_unique<ShapePolygon>(
std::vector<ShapePoint>{ShapePoint{x + width * 2 / 5, y + height * 4 / 5},
ShapePoint{x + width * 3 / 5, y + height * 4 / 5},
ShapePoint{x + width * 3 / 5, y + height},
ShapePoint{x + width * 2 / 5, y + height}},
RGB565(0x80, 0x20, 0x20)));
return std::make_unique<ShapeGroup>(std::move(shapes));
}
std::unique_ptr<Shape> CreateShapeXmasTree(int x, int y, int width,
int height) {
std::vector<std::unique_ptr<Shape>> shapes;
auto tree = CreateShapeTree(x, y, width, height);
// star
shapes.push_back(
CreateShapeStar(x + width / 2, y, width / 10, RGB565(0xff, 0xff, 0x00)));
// deco
for (int sy = 1; sy < 6; ++sy) {
for (int sx = 0; sx <= 5; ++sx) {
int px = x + width / 5 * sx + width / 10 * (sy % 2);
int py = y + height / 8 * sy + height / 16;
if (!tree->Get(ShapePoint(px, py)).first)
continue;
int col = (sx + sy) % 2;
shapes.push_back(std::make_unique<ShapeCircle>(
px, py, width / 20,
col ? RGB565(0xff, 0x00, 0x00) : RGB565(0xff, 0xff, 0x00)));
}
}
int px, py;
// tree
shapes.push_back(std::move(tree));
return std::make_unique<ShapeGroup>(std::move(shapes));
}
// ----------------------------------------------------------------------------
int main() {
DmxDisplay display;
DmxResource resource(550, 350);
ShapeCanvasRGB565 canvas(resource.width(), resource.height(),
resource.pitch());
DmxRect dst_rect_canvas(display.width() / 2 - resource.width() / 2,
display.height() / 2 - resource.height() / 2,
resource.width(), resource.height());
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(CreateShapeXmasTree(50, 50, 200, 250));
shapes.push_back(CreateShapeXmasTree(300, 50, 200, 250));
canvas.UpdateAll(RGB565(0xff, 0xff, 0xff), shapes);
resource.write(canvas.data().data());
DmxUpdate update;
update.Start(10);
DmxRect src_rect(0, 0, resource.width() << 16, resource.height() << 16);
DmxElement element =
DmxElement::AddTo(display, update, 2000 /* layer */, src_rect,
dst_rect_canvas, resource.handle());
update.Sync();
{
char c;
std::cout << "input something to exit:" << std::endl;
std::cin >> c;
}
update.Start(10);
element.Remove(update);
update.Sync();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment