Created
December 21, 2023 13:25
-
-
Save buyoh/3f786fe400f42c881cdc84b1153f4d00 to your computer and use it in GitHub Desktop.
#クリスマスプロ生ちゃん もみの木をdispmanxだけで描画する https://twitter.com/shonen9th/status/1737826399497666618
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ## 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