// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*- // // Example how to display an image with split reveal animation - BOOT VERSION // Runs once at boot and exits // // This requires an external dependency, so install these first before you // can call `make image-example` // sudo apt-get update // sudo apt-get install libgraphicsmagick++-dev libwebp-dev -y /* pour compiler: g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot.cc -o /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot -lrgbmatrix `GraphicsMagick++-config --cppflags --libs` pour lancer: sudo /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png */ #include "led-matrix.h" #include #include #include #include #include #include #include #include #include using rgb_matrix::Canvas; using rgb_matrix::RGBMatrix; using rgb_matrix::FrameCanvas; // Make sure we can exit gracefully when Ctrl-C is pressed. volatile bool interrupt_received = false; static void InterruptHandler(int signo) { interrupt_received = true; } using ImageVector = std::vector; // Given the filename, load the image and scale to the size of the matrix. static ImageVector LoadImageAndScaleImage(const char *filename, int target_width, int target_height) { ImageVector result; ImageVector frames; try { readImages(&frames, filename); } catch (std::exception &e) { if (e.what()) fprintf(stderr, "%s\n", e.what()); return result; } if (frames.empty()) { fprintf(stderr, "No image found."); return result; } // Animated images have partial frames that need to be put together if (frames.size() > 1) { Magick::coalesceImages(&result, frames.begin(), frames.end()); } else { result.push_back(frames[0]); // just a single still image. } for (Magick::Image &image : result) { image.scale(Magick::Geometry(target_width, target_height)); } return result; } // Copy image with split reveal animation effect void CopyImageToCanvasWithSplitReveal(const Magick::Image &image, Canvas *canvas, float progress) { const int matrix_height = canvas->height(); const int matrix_width = canvas->width(); const int half_height = matrix_height / 2; // Calculate how many rows of each half to show based on progress (0.0 to 1.0) int top_rows_to_show = (int)(half_height * progress); int bottom_rows_to_show = (int)(half_height * progress); // Clear the canvas first canvas->Clear(); // Draw top half (sliding down from above) for (int row = 0; row < top_rows_to_show && row < half_height; ++row) { // Calculate source row in the original image int src_row = row; if (src_row < (int)image.rows()) { for (size_t x = 0; x < image.columns() && x < matrix_width; ++x) { const Magick::Color &c = image.pixelColor(x, src_row); if (c.alphaQuantum() < 256) { canvas->SetPixel(x, row, ScaleQuantumToChar(c.redQuantum()), ScaleQuantumToChar(c.greenQuantum()), ScaleQuantumToChar(c.blueQuantum())); } } } } // Draw bottom half (sliding up from below) for (int row = 0; row < bottom_rows_to_show && row < half_height; ++row) { // Calculate destination row (starting from bottom) int dest_row = matrix_height - 1 - row; // Calculate source row in the original image (from bottom half) int src_row = (int)image.rows() - 1 - row; if (src_row >= half_height && src_row < (int)image.rows() && dest_row >= half_height) { for (size_t x = 0; x < image.columns() && x < matrix_width; ++x) { const Magick::Color &c = image.pixelColor(x, src_row); if (c.alphaQuantum() < 256) { canvas->SetPixel(x, dest_row, ScaleQuantumToChar(c.redQuantum()), ScaleQuantumToChar(c.greenQuantum()), ScaleQuantumToChar(c.blueQuantum())); } } } } } // Show image with split reveal animation - BOOT VERSION (runs once and exits) void ShowBootSplitRevealAnimation(const Magick::Image &image, RGBMatrix *matrix) { const int animation_duration_ms = 3000; // 3 seconds animation const int display_duration_ms = 5000; // 5 seconds display const int fps = 60; // 60 frames per second for smooth animation const int frame_delay_ms = 1000 / fps; const int total_frames = animation_duration_ms / frame_delay_ms; FrameCanvas *offscreen_canvas = matrix->CreateFrameCanvas(); printf("Starting boot split reveal animation...\n"); auto start_time = std::chrono::steady_clock::now(); // Animation phase for (int frame = 0; frame <= total_frames && !interrupt_received; ++frame) { // Calculate progress (0.0 to 1.0) float progress = (float)frame / total_frames; // Apply easing function for smoother animation (ease-out) progress = 1.0f - (1.0f - progress) * (1.0f - progress); // Render frame with current progress CopyImageToCanvasWithSplitReveal(image, offscreen_canvas, progress); offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas); // Maintain consistent frame rate std::this_thread::sleep_for(std::chrono::milliseconds(frame_delay_ms)); } auto animation_end = std::chrono::steady_clock::now(); auto animation_duration = std::chrono::duration_cast(animation_end - start_time); printf("Boot animation completed in %ld ms\n", animation_duration.count()); // Display final complete image printf("Displaying boot image for %d seconds...\n", display_duration_ms / 1000); auto display_start = std::chrono::steady_clock::now(); while (!interrupt_received) { auto current_time = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(current_time - display_start); if (elapsed.count() >= display_duration_ms) { break; } // Keep refreshing the display offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } auto total_end = std::chrono::steady_clock::now(); auto total_duration = std::chrono::duration_cast(total_end - start_time); printf("Boot display completed. Total time: %ld ms (%ds animation + %ds display)\n", total_duration.count(), animation_duration_ms / 1000, display_duration_ms / 1000); } int main(int argc, char *argv[]) { Magick::InitializeMagick(*argv); // Check if a filename was provided if (argc != 2) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } const char *filename = argv[1]; // Initialize with hardcoded defaults for your specific hardware RGBMatrix::Options defaults; defaults.hardware_mapping = "moduleair_pinout"; defaults.rows = 64; defaults.cols = 128; defaults.chain_length = 1; defaults.parallel = 1; defaults.row_address_type = 3; defaults.show_refresh_rate = false; // Disable refresh rate display for boot defaults.brightness = 100; defaults.pwm_bits = 1; defaults.panel_type = "FM6126A"; defaults.disable_hardware_pulsing = false; // Set up runtime options rgb_matrix::RuntimeOptions runtime_opt; runtime_opt.gpio_slowdown = 4; // Adjust if needed for your Pi model runtime_opt.daemon = 0; runtime_opt.drop_privileges = 0; // Set to 1 if not running as root // Handle Ctrl+C to exit gracefully signal(SIGTERM, InterruptHandler); signal(SIGINT, InterruptHandler); // Create the matrix with our defaults RGBMatrix *matrix = RGBMatrix::CreateFromOptions(defaults, runtime_opt); if (matrix == NULL) { fprintf(stderr, "Failed to create matrix\n"); return 1; } // Load and process the image ImageVector images = LoadImageAndScaleImage(filename, matrix->width(), matrix->height()); // Display the image with split reveal animation (once only) switch (images.size()) { case 0: // failed to load image fprintf(stderr, "Failed to load image\n"); break; case 1: // single image - show with split reveal animation ShowBootSplitRevealAnimation(images[0], matrix); break; default: // animated image - just show first frame with split reveal printf("Animated image detected, using first frame for boot animation\n"); ShowBootSplitRevealAnimation(images[0], matrix); break; } // Clean up and exit printf("Boot animation finished. Cleaning up and exiting...\n"); matrix->Clear(); delete matrix; return 0; }