hzeller / rpi-rgb-led-matrix

Controlling up to three chains of 64x64, 32x32, 16x32 or similar RGB LED displays using Raspberry Pi GPIO
GNU General Public License v2.0
3.65k stars 1.16k forks source link

Multiple canvases (canvi, to be pedantic) #994

Open shanerooni opened 4 years ago

shanerooni commented 4 years ago

I'm fairly new to the pi 4, adafruit hat, and have only done some html5+js+css programming. i'm trying to make a large 3x4 matrix of 32x64 panels to display a huge finish line stopwatch for a kids running series where i'm a volunteer.

i've done well to get my 2 test displays to work well while waiting for the other 12 displays to arrive. the learning curve has been steep, but the examples have been great. there are a few questions i have, but the multiple canvi is the main issue.

first, i can see how a canvas is declared, and have had no issues manipulating it the way i want to display or scroll text, run the stopwatch, etc. however, i believe i'll have extra space on the display to show other information. am i able to, in the case of my 32x128 display, create two canvi and define them so that one is - let's say - 32x32 on the far left, and the other is 32x96 on the right?

here are a few other questions somewhat unrelated, but i didn't want to open a bunch of strings just to have some basic questions answered. i know you're busy, so feel free to only address the multi-canvas issue if possible.

for my particular panels to work, i have to use --led-multiplexing=1. i have no idea why this works, and i only found out because someone on amazon that bought the same panels put it in their review. would you be able to briefly explain why i need this tag? (otherwise whatever is on my display is repeated and garbled)

i will be out in a cross-country field for the running series. am i able to connect my phone via bluetooth or wifi to the pi and send it commands (start, stop, reset, scrolling messages, etc.). i was going to try to create a simple app with the pre-loaded commands that i need for easy access. my android phone has hotspot if that is required, but i won't be able to connect the phone and pi to the same "home" wifi like i have set up in my home office.

the bdf font has been giving me some headaches. is there a way to scale fonts or easily create my own? i'm thinking when i have the full 3x4 matrix, i will need fonts that are at least ~70 pixels high. i can't find any existing fonts that large, but it seems like a waste of time to painstakingly create my own custom-sized font.

i sincerely appreciate any help you are able to provide.

hzeller commented 4 years ago

The simplest case of course is to just offset whatever you want to show (e.g. text) in the other region; but I can see how it would be conceptually simpler to just think of multiple regions to be 'windows' on the big canvas.

If you want to think in terms of 'windows', I suggest to make a delegating canvas. A class that implements the canvas interface and for instance retuns a particular size it has (width() and height() impelementation).

Then in SetPixel() it just delegates the call to the underlying canvas (e.g. the matrix canvas), and can apply an offset to it.

A c++ implementation would roughly look like this

class WindowCanvas : public rgb_matrix::Canvas {
public:
  WindowCanvas(rgb_matrix::Canvas *delegatee,
               int width, int height,
               int offset_x, int offset_y)
    : delegatee_(delegatee), width_(width), height_(height),
      offset_x_(offset_x), offset_y_(offset_y) {}

  virtual int width() const { return width_; }
  virtual int height() const { return height_; }
  virtual void SetPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
    if (x < 0 || x > width_ || y < 0 || y > height_) return;  // do clipping
    delegatee_->SetPixel(x + offset_x_, y + offset_y_, r, g, b);
  }
  virtual void Clear() { delegatee_->Clear(); }
  virtual void Fill(uint8_t r, uint8_t g, uint8_t b) {
    delegatee_->Fill(r, g, b);
  }

private:
  rgb_matrix::Canvas *const delegatee_;
  const int width_;
  const int height_;
  const int offset_x_;
  const int offset_y_;
};

Now, you can use that; say the frame canvas from your is rgbmatrix_canvas

You now can do

Canvas *left_window = new WindowCanvas(rgbmatrix_window, 32, 32, 0, 0);
Canvas *right_window = new WindowCanvas(rgbmatrix_wqindow, 96, 32, 32, 0);

And can regard each canvas as independent 'window' that also does clipping properly.

hzeller commented 4 years ago

--led-multiplexing, and why you need it for some panels is described in the README.

manuel1507 commented 3 years ago

HI, I'm trying the "windows" approach and made some changes to the text-scroller example, but I got stuck when trying to swap the left_window on vsync:

left_window = canvas->SwapOnVSync(left_window);

The functions expects a FrameCanvas.

Any help would be appreciated, Thanks

hzeller commented 3 years ago

Yes, you have to swap the whole FrameCanvas. This is the underlying hardware buffer.

So what you would do is to change all your Windows for the next animation step, and then swap the underlying canvas.

manuel1507 commented 3 years ago

Thank you very much. I’ll will give it a try tomorrow!

manuel1507 commented 3 years ago

Hi, implemented a draft of the textscroller example with "windows":

RGBMatrix *canvas = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt); if (canvas == NULL) return 1;

const bool all_extreme_colors = (matrix_options.brightness == 100) && FullSaturation(color) && FullSaturation(bg_color) && FullSaturation(outline_color); if (all_extreme_colors) canvas->SetPWMBits(1);

signal(SIGTERM, InterruptHandler); signal(SIGINT, InterruptHandler);`

printf("CTRL-C for exit.\n");

// Create a new canvas to be used with led_matrix_swap_on_vsync FrameCanvas offscreen_canvas = canvas->CreateFrameCanvas(); Canvas left_window = new WindowCanvas(offscreen_canvas, 32, 32, 0, 0);

const int scroll_direction = (speed >= 0) ? -1 : 1; speed = fabs(speed); int delay_speed_usec = 1000000; if (speed > 0) { delay_speed_usec = 1000000 / speed / font.CharacterWidth('W'); }

if (!xorigin_configured) { if (speed == 0) { // There would be no scrolling, so text would never appear. Move to front. x_orig = with_outline ? 1 : 0; } else { x_orig = scroll_direction < 0 ? canvas->width() : 0; } }

int x = x_orig; int y = y_orig; int length = 0;

struct timespec next_frame = {0, 0};

uint frame_counter = 0; while (!interrupt_received && loops != 0) { ++frame_counter; left_window->Fill(bg_color.r, bg_color.g, bg_color.b); const bool draw_on_frame = (blink_on <= 0) || (frame_counter % (blink_on + blink_off) < (uint)blink_on);

if (draw_on_frame) {
  if (outline_font) {
    // The outline font, we need to write with a negative (-2) text-spacing,
    // as we want to have the same letter pitch as the regular text that
    // we then write on top.
    rgb_matrix::DrawText(left_window, *outline_font,
                         x - 1, y + font.baseline(),
                         outline_color, NULL,
                         line.c_str(), letter_spacing - 2);
  }

  // length = holds how many pixels our text takes up
  length = rgb_matrix::DrawText(left_window, font,
                                x, y + font.baseline(),
                                color, NULL,
                                line.c_str(), letter_spacing);
}

x += scroll_direction;
if ((scroll_direction < 0 && x + length < 0) ||
    (scroll_direction > 0 && x > canvas->width())) {
  x = x_orig + ((scroll_direction > 0) ? -length : 0);
  if (loops > 0) --loops;
}

// Make sure render-time delays are not influencing scroll-time
if (speed > 0) {
  if (next_frame.tv_sec == 0) {
    // First time. Start timer, but don't wait.
    clock_gettime(CLOCK_MONOTONIC, &next_frame);
  } else {
    add_micros(&next_frame, delay_speed_usec);
    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame, NULL);
  }
}
// Swap the offscreen_canvas with canvas on vsync, avoids flickering
offscreen_canvas = canvas->SwapOnVSync(offscreen_canvas);
if (speed <= 0) pause();  // Nothing to scroll.

}

// Finished. Shut down the RGB matrix. canvas->Clear(); delete canvas;

return 0; }

but there's a lot of flickering. Any idea?

thanks,

AnAxNu commented 10 months ago

The finished class:

class WindowCanvas : public rgb_matrix::Canvas {
public:

  virtual ~WindowCanvas () {}

  WindowCanvas (rgb_matrix::Canvas *delegatee,int width, int height,int offset_x, int offset_y): delegatee_(delegatee), width_(width), height_(height),offset_x_(offset_x), offset_y_(offset_y) {}

  virtual int width() const {
    return width_;
  }

  virtual int height() const {
    return height_;
  }

  // Set pixel at coordinate (x,y) with given color. Pixel (0,0) is the
  // top left corner.
  // Each color is 8 bit (24bpp), 0 black, 255 brightest.
  virtual void SetPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
    if (x < 0 || x > width_ || y < 0 || y > height_) return;  // do clipping
    delegatee_->SetPixel(x + offset_x_, y + offset_y_, r, g, b);
  }

  // Clear screen to be all black.
  virtual void Clear() {
    delegatee_->Clear();
    return;
  }

  // Fill screen with given 24bpp color.
  // note: this will fill all screen since its not limited to the canvas size
  virtual void Fill(uint8_t r, uint8_t g, uint8_t b) {
    delegatee_->Fill(r, g, b);
    return;
  }

private:
  rgb_matrix::Canvas *const delegatee_;
  const int width_;
  const int height_;
  const int offset_x_;
  const int offset_y_;
};