diff --git a/Makefile b/Makefile index 7b73319..9a1f77f 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,8 @@ $(RGB_LIBRARY): FORCE clean: $(MAKE) -C lib clean + $(MAKE) -C examples-api-use clean + $(MAKE) -C utils clean $(MAKE) -C $(PYTHON_LIB_DIR) clean build-python: $(RGB_LIBRARY) diff --git a/examples-api-use/demo-main.cc b/examples-api-use/demo-main.cc index 1d4d3a2..82cd7e0 100644 --- a/examples-api-use/demo-main.cc +++ b/examples-api-use/demo-main.cc @@ -1017,22 +1017,12 @@ static int usage(const char *progname) { fprintf(stderr, "usage: %s -D [optional parameter]\n", progname); fprintf(stderr, "Options:\n"); - RGBMatrix::Options::FlagUsageMessage(); fprintf(stderr, - "\t-L : 'Large' display, composed out of 4 times 32x32\n" - "\t-p : Bits used for PWM. Something between 1..11\n" "\t-D : Always needs to be set\n" - "\t-d : run as daemon. Use this when starting " - "in\n" - "\t /etc/init.d, but also when " - "running without\n" - "\t terminal (e.g. cron).\n" "\t-t : Run for these number of seconds, then exit.\n" - "\t (if neither -d nor -t are supplied, " - "waits for )\n" - "\t-b : Sets brightness percent. Default: 100.\n" "\t-R : Sets the rotation of matrix. " "Allowed: 0, 90, 180, 270. Default: 0.\n"); + rgb_matrix::PrintMatrixOptions(stderr); fprintf(stderr, "Demos, choosen with -D\n"); fprintf(stderr, "\t0 - some rotating square\n" "\t1 - forward scrolling an image (-m )\n" @@ -1052,36 +1042,23 @@ static int usage(const char *progname) { } int main(int argc, char *argv[]) { - GPIO io; - bool as_daemon = false; + // First things first: create matrix and take command line flags. + RGBMatrix *matrix = CreateMatrixFromFlags(&argc, &argv); + int runtime_seconds = -1; int demo = -1; - RGBMatrix::Options options; int scroll_ms = 30; - int pwm_bits = -1; - int brightness = 100; int rotation = 0; - bool large_display = false; - bool do_luminance_correct = true; - const char *demo_parameter = NULL; - - // First, let's consume the flags for the options. - if (!options.InitializeFromFlags(&argc, &argv)) { - return usage(argv[0]); - } + bool any_deprecated_option = false; int opt; - while ((opt = getopt(argc, argv, "dlD:t:r:P:c:p:b:m:LR:")) != -1) { + while ((opt = getopt(argc, argv, "dD:t:r:P:c:p:b:m:LR:")) != -1) { switch (opt) { case 'D': demo = atoi(optarg); break; - case 'd': - as_daemon = true; - break; - case 't': runtime_seconds = atoi(optarg); break; @@ -1090,49 +1067,39 @@ int main(int argc, char *argv[]) { scroll_ms = atoi(optarg); break; - case 'p': - pwm_bits = atoi(optarg); - break; - - case 'b': - brightness = atoi(optarg); - break; - - case 'l': - do_luminance_correct = !do_luminance_correct; - break; - - case 'L': - // The 'large' display assumes a chain of four displays with 32x32 - options.chain_length = 4; - options.rows = 32; - large_display = true; - break; - case 'R': rotation = atoi(optarg); break; - // These used to be options we understood, but deprecate now. Accept them - // for now, but tell the user. + // These used to be options we understood, but deprecated now. Tell user. case 'r': - options.rows = atoi(optarg); - fprintf(stderr, TERM_ERR "-r is a deprecated option. " - "Please use --led-rows=%d instead!\n" TERM_NORM, options.rows); + fprintf(stderr, "-r is a deprecated option. " + "Please use --led-rows=... instead!\n"); + any_deprecated_option = true; break; case 'P': - options.parallel = atoi(optarg); - fprintf(stderr, TERM_ERR "-P is a deprecated option. " - "Please use --led-parallel=%d instead!\n" TERM_NORM, - options.parallel); + fprintf(stderr, "-P is a deprecated option. " + "Please use --led-parallel=... instead!\n"); + any_deprecated_option = true; break; case 'c': - options.chain_length = atoi(optarg); - fprintf(stderr, TERM_ERR "-c is a deprecated option. " - "Please use --led-chain=%d instead!\n" TERM_NORM, - options.chain_length); + fprintf(stderr, "-c is a deprecated option. " + "Please use --led-chain=... instead!\n"); + any_deprecated_option = true; + break; + + case 'p': + fprintf(stderr, "-p is a deprecated option. " + "Please use --led-pwm-bits=... instead!\n"); + any_deprecated_option = true; + break; + + case 'b': + fprintf(stderr, "-b is a deprecated option. " + "Please use --led-brightness=... instead!\n"); + any_deprecated_option = true; break; default: /* '?' */ @@ -1140,69 +1107,31 @@ int main(int argc, char *argv[]) { } } + if (any_deprecated_option) + return usage(argv[0]); + if (optind < argc) { demo_parameter = argv[optind]; } - std::string err; - if (!options.Validate(&err)) { - fprintf(stderr, "%s", err.c_str()); - return 1; - } - if (demo < 0) { fprintf(stderr, TERM_ERR "Expected required option -D \n" TERM_NORM); return usage(argv[0]); } - if (brightness < 1 || brightness > 100) { - fprintf(stderr, TERM_ERR "Brightness is outside usable range.\n" TERM_NORM); - return 1; - } - if (rotation % 90 != 0) { fprintf(stderr, TERM_ERR "Rotation %d not allowed! " "Only 0, 90, 180 and 270 are possible.\n" TERM_NORM, rotation); return 1; } - if (getuid() != 0) { - fprintf(stderr, TERM_ERR "Must run as root to be able to access /dev/mem\n" - "Prepend 'sudo' to the command:\n\tsudo %s ...\n" TERM_NORM, - argv[0]); - return 1; - } - - // Initialize GPIO pins. This might fail when we don't have permissions. - if (!io.Init()) - return 1; - - // Start daemon before we start any threads. - if (as_daemon) { - if (fork() != 0) - return 0; - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - } - // The matrix, our 'frame buffer' and display updater. - RGBMatrix *matrix = new RGBMatrix(&io, options); - matrix->set_luminance_correct(do_luminance_correct); - matrix->SetBrightness(brightness); - if (pwm_bits >= 0 && !matrix->SetPWMBits(pwm_bits)) { - fprintf(stderr, "Invalid range of pwm-bits\n"); + if (matrix == NULL) return 1; - } LinkedTransformer *transformer = new LinkedTransformer(); matrix->SetTransformer(transformer); - if (large_display) { - // Mapping the coordinates of a 32x128 display mapped to a square of 64x64 - transformer->AddTransformer(new LargeSquare64x64Transformer()); - } - if (rotation > 0) { transformer->AddTransformer(new RotateTransformer(rotation)); } @@ -1278,9 +1207,7 @@ int main(int argc, char *argv[]) { // Now, the image generation runs in the background. We can do arbitrary // things here in parallel. In this demo, we're essentially just // waiting for one of the conditions to exit. - if (as_daemon) { - sleep(runtime_seconds > 0 ? runtime_seconds : INT_MAX); - } else if (runtime_seconds > 0) { + if (runtime_seconds > 0) { sleep(runtime_seconds); } else { // Things are set up. Just wait for to be pressed. diff --git a/examples-api-use/minimal-example.cc b/examples-api-use/minimal-example.cc index 80672f1..2731a71 100644 --- a/examples-api-use/minimal-example.cc +++ b/examples-api-use/minimal-example.cc @@ -36,28 +36,10 @@ static void DrawOnCanvas(Canvas *canvas) { } int main(int argc, char *argv[]) { - /* - * Set up GPIO pins. This fails when not running as root. - */ - GPIO io; - if (!io.Init()) + Canvas *canvas = rgb_matrix::CreateMatrixFromFlags(&argc, &argv); + if (canvas == NULL) return 1; - /* - * Set up the RGBMatrix. It implements a 'Canvas' interface. - */ - RGBMatrix::Options options; - if (!options.InitializeFromFlags(&argc, &argv)) { - options.FlagUsageMessage(); - return 1; - } - std::string err; - if (!options.Validate(&err)) { - fprintf(stderr, "%s", err.c_str()); - return 1; - } - Canvas *canvas = new RGBMatrix(&io, options); - DrawOnCanvas(canvas); // Using the canvas. // Animation finished. Shut down the RGB matrix. diff --git a/examples-api-use/text-example.cc b/examples-api-use/text-example.cc index 582ecf6..02d0d36 100644 --- a/examples-api-use/text-example.cc +++ b/examples-api-use/text-example.cc @@ -20,7 +20,7 @@ static int usage(const char *progname) { fprintf(stderr, "Reads text from stdin and displays it. " "Empty string: clear screen\n"); fprintf(stderr, "Options:\n"); - RGBMatrix::Options::FlagUsageMessage(); + rgb_matrix::PrintMatrixOptions(stderr); fprintf(stderr, "\t-f : Use given font.\n" "\t-b : Sets brightness percent. Default: 100.\n" @@ -35,18 +35,14 @@ static bool parseColor(Color *c, const char *str) { } int main(int argc, char *argv[]) { + RGBMatrix *canvas = rgb_matrix::CreateMatrixFromFlags(&argc, &argv); + Color color(255, 255, 0); const char *bdf_font_file = NULL; - RGBMatrix::Options options; int x_orig = 0; int y_orig = -1; int brightness = 100; - // First, let's consume the flags for the options. - if (!options.InitializeFromFlags(&argc, &argv)) { - return usage(argv[0]); - } - int opt; while ((opt = getopt(argc, argv, "x:y:f:C:b:")) != -1) { switch (opt) { @@ -65,6 +61,9 @@ int main(int argc, char *argv[]) { } } + if (canvas == NULL) + return 1; + if (bdf_font_file == NULL) { fprintf(stderr, "Need to specify BDF font-file with -f\n"); return usage(argv[0]); @@ -79,28 +78,11 @@ int main(int argc, char *argv[]) { return usage(argv[0]); } - std::string err; - if (!options.Validate(&err)) { - fprintf(stderr, "%s", err.c_str()); - return 1; - } - if (brightness < 1 || brightness > 100) { fprintf(stderr, "Brightness is outside usable range.\n"); return 1; } - /* - * Set up GPIO pins. This fails when not running as root. - */ - GPIO io; - if (!io.Init()) - return 1; - - /* - * Set up the RGBMatrix. It implements a 'Canvas' interface. - */ - RGBMatrix *canvas = new RGBMatrix(&io, options); canvas->SetBrightness(brightness); bool all_extreme_colors = brightness == 100; diff --git a/include/led-matrix.h b/include/led-matrix.h index 52419f8..92d3820 100644 --- a/include/led-matrix.h +++ b/include/led-matrix.h @@ -30,9 +30,34 @@ #include "transformer.h" namespace rgb_matrix { +class RGBMatrix; class FrameCanvas; // Canvas for Double- and Multibuffering namespace internal { class Framebuffer; } +// Convenience factory utility to create a Matrix and set values from the +// command line. You pass it a pointer to the argc and argv of main, it +// extracts the relevant options and leaves the remaining options. +// +// Example use: +/* +using rgb_matrix::RGBMatrix; +int main(int argc, char **argv) { + RGBMatrix *matrix = rgb_matrix::CreateMatrixFromFlags(&argc, &argv); + if (matrix == NULL) { + PrintMatrixOptions(stderr); + return 1; + } + + // Do your own command line handling with the remaining options. + + // .. now use matrix + + delete matrix; // Make sure to delete it in the end. +} +*/ +RGBMatrix *CreateMatrixFromFlags(int *argc, char ***argv); +void PrintMatrixOptions(FILE *out); + // The RGB matrix provides the framebuffer and the facilities to constantly // update the LED matrix. // @@ -51,23 +76,9 @@ public: struct Options { Options(); // Creates a default option set. - // Validate the options and possibly output a message to + // Validate the options and possibly output a message to string. bool Validate(std::string *err); - // --led_rows, --led_chain, --led_parallel - // This modifies the argc and argv, so use in main such as - // int main(int argc, char *argv[]) { - // RGBMatrix::Options options; - // if (!options.InitializeFromFlags(&argc, &argv)) { - // return 1; - // } - // // ... now do the relevant stuff. - // } - bool InitializeFromFlags(int *argc, char ***argv); - - // Usage message that explains the available flags. - static void FlagUsageMessage(); - // The "rows" are the number // of rows supported by the display, so 32 or 16. Default: 32. int rows; @@ -81,6 +92,15 @@ public: // also be 2 or 3. The effective number of pixels in vertical direction is // then thus rows * parallel. Default: 1 int parallel; + + // Set PWM bits used for output. Default is 11, but if you only deal with + // limited comic-colors, 1 might be sufficient. Lower require less CPU and + // increases refresh-rate. + int pwm_bits; + + // The initial brightness of the panel in percent. Valid range is 1..100 + // Default: 100 + int brightness; }; // Create an RGBMatrix. diff --git a/lib/led-matrix.cc b/lib/led-matrix.cc index 9151efd..34b4927 100644 --- a/lib/led-matrix.cc +++ b/lib/led-matrix.cc @@ -120,11 +120,14 @@ private: }; // Some defaults. See options-initialize.cc for the command line parsing. -RGBMatrix::Options::Options() : rows(32), chain_length(1), parallel(1) {} +RGBMatrix::Options::Options() + : rows(32), chain_length(1), parallel(1), pwm_bits(11), brightness(100) {} RGBMatrix::RGBMatrix(GPIO *io, const Options &options) : rows_(options.rows), chained_displays_(options.chain_length), parallel_displays_(options.parallel), + pwm_bits_(options.pwm_bits), + brightness_(options.brightness), io_(NULL), updater_(NULL) { SetTransformer(NULL); active_ = CreateFrameCanvas(); diff --git a/lib/options-initialize.cc b/lib/options-initialize.cc index 2e6d664..f8cf869 100644 --- a/lib/options-initialize.cc +++ b/lib/options-initialize.cc @@ -14,15 +14,31 @@ // along with this program. If not, see #include "led-matrix.h" -#include + #include #include +#include +#include +#include +#include +#include + #include namespace rgb_matrix { namespace { typedef char** argv_iterator; +static bool ConsumeBoolFlag(const char *flag_name, const argv_iterator &pos, + bool *result_value) { + const char *option = *pos; + const size_t flag_len = strlen(flag_name); + if (strncmp(option, flag_name, flag_len) != 0) + return false; // not consumed. + *result_value = !*result_value; + return true; +} + static bool ConsumeIntFlag(const char *flag_name, argv_iterator &pos, const argv_iterator end, int *result_value, int *error) { @@ -53,7 +69,16 @@ static bool ConsumeIntFlag(const char *flag_name, return true; // consumed. } -static bool OptFlagInit(int &argc, char **&argv, RGBMatrix::Options *mopts) { +struct RuntimeOptions { + RuntimeOptions() : as_daemon(false), drop_privileges(false) {} + + bool as_daemon; + bool drop_privileges; +}; + +static bool FlagInit(int &argc, char **&argv, + RGBMatrix::Options *mopts, + RuntimeOptions *ropts) { argv_iterator it = &argv[0]; argv_iterator end = it + argc; @@ -71,6 +96,14 @@ static bool OptFlagInit(int &argc, char **&argv, RGBMatrix::Options *mopts) { continue; if (ConsumeIntFlag("--led-parallel", it, end, &mopts->parallel, &err)) continue; + if (ConsumeIntFlag("--led-brightness", it, end, &mopts->brightness, &err)) + continue; + if (ConsumeIntFlag("--led-pwm-bits", it, end, &mopts->pwm_bits, &err)) + continue; + if (ConsumeBoolFlag("--led-daemon", it, &ropts->as_daemon)) + continue; + if (ConsumeBoolFlag("--led-drop-privs", it, &ropts->drop_privileges)) + continue; } unused_options.push_back(*it); } @@ -86,43 +119,118 @@ static bool OptFlagInit(int &argc, char **&argv, RGBMatrix::Options *mopts) { } return true; } -} // namespace -bool RGBMatrix::Options::InitializeFromFlags(int *argc, char ***argv) { - // Unfortunately, we can't use getopt_long(), as it does not provide a - // way to only fish out some of the flags and leave the rest as-is without - // much complaining. So we have to do this here ourselves. - return OptFlagInit(*argc, *argv, this); +static bool drop_privs(const char *priv_user, const char *priv_group) { + uid_t ruid, euid, suid; + if (getresuid(&ruid, &euid, &suid) >= 0) { + if (euid != 0) // not root anyway. No priv dropping. + return true; + } + + struct group *g = getgrnam(priv_group); + if (g == NULL) { + perror("group lookup."); + return false; + } + if (setresgid(g->gr_gid, g->gr_gid, g->gr_gid) != 0) { + perror("setresgid()"); + return false; + } + struct passwd *p = getpwnam(priv_user); + if (p == NULL) { + perror("user lookup."); + return false; + } + if (setresuid(p->pw_uid, p->pw_uid, p->pw_uid) != 0) { + perror("setresuid()"); + return false; + } + return true; } -void RGBMatrix::Options::FlagUsageMessage() { - fprintf(stderr, - "\t--led-rows : Panel rows. 8, 16, 32 or 64. " +} // namespace + +// Public interface. +RGBMatrix *CreateMatrixFromFlags(int *argc, char ***argv) { + RGBMatrix::Options mopt; + RuntimeOptions ropt; + if (!FlagInit(*argc, *argv, &mopt, &ropt)) { + return NULL; + } + + std::string error; + if (!mopt.Validate(&error)) { + fprintf(stderr, "%s\n", error.c_str()); + return NULL; + } + + if (getuid() != 0) { + fprintf(stderr, "Must run as root to be able to access /dev/mem\n" + "Prepend 'sudo' to the command:\n\tsudo %s ...\n", (*argv)[0]); + return NULL; + } + + static GPIO io; // This static var is a little bit icky. + if (!io.Init()) { + return NULL; + } + + if (ropt.as_daemon && daemon(1, 0) != 0) { + perror("Failed to become daemon"); + } + + RGBMatrix *result = new RGBMatrix(&io, mopt); + + if (ropt.drop_privileges) { + drop_privs("daemon", "daemon"); + } + + return result; +} + +void PrintMatrixOptions(FILE *out) { + fprintf(out, + "\t--led-rows= : Panel rows. 8, 16, 32 or 64. " "Default: 32\n" - "\t--led-parallel : For Plus-models or RPi2: parallel " + "\t--led-chain= : Number of daisy-chained panels. " + "Default: 1.\n" + "\t--led-parallel= : For A/B+ models or RPi2,3b: parallel " "chains. 1..3. Default: 1\n" - "\t--led-chain : Number of daisy-chained boards. " - "Default: 1.\n"); + "\t--led-pwm-bits=<1..11> : PWM bits. Default: 11\n" + "\t--led-brightness=: Brightness in percent. Default: 100.\n" + "\t--led-drop-privs : Drop privileges from 'root'.\n" + "\t--led-daemon : Make the process run as daemon.\n"); } bool RGBMatrix::Options::Validate(std::string *err) { - bool any_error = false; + bool success = true; if (rows != 8 && rows != 16 && rows != 32 && rows != 64) { err->append("Invalid number or panel rows. " "Should be one of 8, 16, 32 or 64\n"); - any_error = true; + success = false; } if (chain_length < 1) { err->append("Chain-length outside usable range\n"); - any_error = true; + success = false; } if (parallel < 1 || parallel > 3) { err->append("Parallel outside usable range.\n"); - any_error = true; + success = false; } - return !any_error; + + if (brightness < 1 || brightness > 100) { + err->append("Brightness is outside usable range.\n"); + success = false; + } + + if (pwm_bits <= 0 || pwm_bits > 11) { + err->append("Invalid range of pwm-bits\n"); + success = false; + } + + return success; } } // namespace rgb_matrix diff --git a/utils/led-image-viewer.cc b/utils/led-image-viewer.cc index d130d83..08d3105 100644 --- a/utils/led-image-viewer.cc +++ b/utils/led-image-viewer.cc @@ -151,62 +151,47 @@ static void DisplayAnimation(const std::vector &frames, static int usage(const char *progname) { fprintf(stderr, "usage: %s [options] \n", progname); fprintf(stderr, "Options:\n"); - RGBMatrix::Options::FlagUsageMessage(); - fprintf(stderr, - "\t-L : Large 64x64 display made " - "from four 32x32 in a chain\n" - "\t-d : Run as daemon.\n" - "\t-b : Sets brightness percent. " - "Default: 100.\n"); + rgb_matrix::PrintMatrixOptions(stderr); return 1; } int main(int argc, char *argv[]) { Magick::InitializeMagick(*argv); + RGBMatrix *const matrix = rgb_matrix::CreateMatrixFromFlags(&argc, &argv); - RGBMatrix::Options options; - int pwm_bits = -1; - int brightness = 100; - bool large_display = false; // example for using Transformers - bool as_daemon = false; - - // First, let's consume the flags for the options. - if (!options.InitializeFromFlags(&argc, &argv)) { - return usage(argv[0]); - } - + // These used to be options we understood, but deprecate now. Accept them + // for now, but tell the user. + bool any_deprecated_option = false; int opt; - while ((opt = getopt(argc, argv, "r:P:c:p:b:dL")) != -1) { + while ((opt = getopt(argc, argv, "r:P:c:p:b:d")) != -1) { switch (opt) { - case 'p': pwm_bits = atoi(optarg); break; - case 'd': as_daemon = true; break; - case 'b': brightness = atoi(optarg); break; - case 'L': - options.chain_length = 4; - options.rows = 32; - large_display = true; - break; - - // These used to be options we understood, but deprecate now. Accept them - // for now, but tell the user. case 'r': - options.rows = atoi(optarg); - fprintf(stderr, TERM_ERR "-r is a deprecated option. " - "Please use --led-rows=%d instead!\n" TERM_NORM, options.rows); + fprintf(stderr, "-r is a deprecated option. " + "Please use --led-rows=... instead!\n"); + any_deprecated_option = true; break; case 'P': - options.parallel = atoi(optarg); - fprintf(stderr, TERM_ERR "-P is a deprecated option. " - "Please use --led-parallel=%d instead!\n" TERM_NORM, - options.parallel); + fprintf(stderr, "-P is a deprecated option. " + "Please use --led-parallel=... instead!\n"); + any_deprecated_option = true; break; case 'c': - options.chain_length = atoi(optarg); - fprintf(stderr, TERM_ERR "-c is a deprecated option. " - "Please use --led-chain=%d instead!\n" TERM_NORM, - options.chain_length); + fprintf(stderr, "-c is a deprecated option. " + "Please use --led-chain=... instead!\n"); + any_deprecated_option = true; + break; + + case 'p': + fprintf(stderr, "-p is a deprecated option. " + "Please use --led-pwm-bits=... instead!\n"); + any_deprecated_option = true; + break; + case 'b': + fprintf(stderr, "-b is a deprecated option. " + "Please use --led-brightness=... instead!\n"); + any_deprecated_option = true; break; default: @@ -214,56 +199,19 @@ int main(int argc, char *argv[]) { } } - std::string err; - if (!options.Validate(&err)) { - fprintf(stderr, "%s", err.c_str()); - return 1; - } - - if (brightness < 1 || brightness > 100) { - fprintf(stderr, "Brightness is outside usable range.\n"); + if (any_deprecated_option) return usage(argv[0]); - } if (optind >= argc) { fprintf(stderr, "Expected image filename.\n"); return usage(argv[0]); } + if (matrix == NULL) + return 1; + const char *filename = argv[optind]; - /* - * Set up GPIO pins. This fails when not running as root. - */ - GPIO io; - if (!io.Init()) - return 1; - - // Start daemon before we start any threads. - if (as_daemon) { - if (fork() != 0) - return 0; - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - } - - RGBMatrix *const matrix = new RGBMatrix(&io, options); - if (pwm_bits >= 0 && !matrix->SetPWMBits(pwm_bits)) { - fprintf(stderr, "Invalid range of pwm-bits\n"); - return 1; - } - - matrix->SetBrightness(brightness); - - // Here is an example where to add your own transformer. In this case, we - // just to the chain-of-four-32x32 => 64x64 transformer, but just use any - // of the transformers in transformer.h or write your own. - if (large_display) { - // Mapping the coordinates of a 32x128 display mapped to a square of 64x64 - matrix->SetTransformer(new rgb_matrix::LargeSquare64x64Transformer()); - } - std::vector sequence_pics; if (!LoadAnimation(filename, matrix->width(), matrix->height(), &sequence_pics)) {