From b973db14ee0dd967724bd6e9c519f8440e643519 Mon Sep 17 00:00:00 2001 From: mrfaptastic <12006953+mrfaptastic@users.noreply.github.com> Date: Mon, 10 Aug 2020 21:47:13 +0100 Subject: [PATCH] Updated GIF example --- examples/AnimatedGIF/AnimatedGIF.ino | 136 --- examples/AnimatedGIF/FSBrowser.h | 179 ---- examples/AnimatedGIF/FilenameFunctions.cpp | 169 ---- examples/AnimatedGIF/FilenameFunctions.h | 21 - examples/AnimatedGIF/GifDecoder.h | 148 ---- examples/AnimatedGIF/GifDecoder_Impl.h | 806 ------------------ examples/AnimatedGIF/LICENSE.txt | 21 - examples/AnimatedGIF/LzwDecoder_Impl.h | 169 ---- .../AnimatedGIF/data/Text_Animations.txt.txt | 1 - .../AnimatedGIFPanel.ino | 0 .../data/gifs/ezgif.com-pacmn.gif | Bin 44831 -> 0 bytes .../data/gifs/loading.io-64x32px.gif | Bin 39645 -> 0 bytes .../{AnimatedGIFPanel => }/README.md | 0 .../data/gifs}/ezgif.com-pacmn.gif | Bin .../data/gifs}/loading.io-64x32px.gif | Bin .../data/gifs/matrix-spin.gif | Bin .../data/gifs/shock.gif | Bin 17 files changed, 1650 deletions(-) delete mode 100644 examples/AnimatedGIF/AnimatedGIF.ino delete mode 100644 examples/AnimatedGIF/FSBrowser.h delete mode 100644 examples/AnimatedGIF/FilenameFunctions.cpp delete mode 100644 examples/AnimatedGIF/FilenameFunctions.h delete mode 100644 examples/AnimatedGIF/GifDecoder.h delete mode 100644 examples/AnimatedGIF/GifDecoder_Impl.h delete mode 100644 examples/AnimatedGIF/LICENSE.txt delete mode 100644 examples/AnimatedGIF/LzwDecoder_Impl.h delete mode 100644 examples/AnimatedGIF/data/Text_Animations.txt.txt rename examples/AnimatedGIFPanel/{AnimatedGIFPanel => }/AnimatedGIFPanel.ino (100%) delete mode 100644 examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif delete mode 100644 examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/loading.io-64x32px.gif rename examples/AnimatedGIFPanel/{AnimatedGIFPanel => }/README.md (100%) rename examples/{AnimatedGIF/data => AnimatedGIFPanel/data/gifs}/ezgif.com-pacmn.gif (100%) rename examples/{AnimatedGIF/data => AnimatedGIFPanel/data/gifs}/loading.io-64x32px.gif (100%) rename examples/AnimatedGIFPanel/{AnimatedGIFPanel => }/data/gifs/matrix-spin.gif (100%) rename examples/AnimatedGIFPanel/{AnimatedGIFPanel => }/data/gifs/shock.gif (100%) diff --git a/examples/AnimatedGIF/AnimatedGIF.ino b/examples/AnimatedGIF/AnimatedGIF.ino deleted file mode 100644 index a5830d2..0000000 --- a/examples/AnimatedGIF/AnimatedGIF.ino +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Written by: Craig A. Lindley - * - * Copyright (c) 2014 Craig A. Lindley - * Refactoring by Louis Beaudoin (Pixelmatix) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include - -RGB64x32MatrixPanel_I2S_DMA matrix; - - -#include "GifDecoder.h" -#include "FilenameFunctions.h" - -/* GIF files for this particular example need to be put into the 'data' directoy of the - * sketch and saved to the ESP32 using the "ESP32 Sketch Data Upload" tool in Arduino. - * - * URL: https://github.com/me-no-dev/arduino-esp32fs-plugin - * - */ -#define GIF_DIRECTORY "/" -#define DISPLAY_TIME_SECONDS 5 - -// Gif sizes should match exactly that of the RGB Matrix display. -const uint8_t GIFWidth = 64; -const uint8_t GIFHeight = 32; - -/* template parameters are maxGifWidth, maxGifHeight, lzwMaxBits - * - * The lzwMaxBits value of 12 supports all GIFs, but uses 16kB RAM - * lzwMaxBits can be set to 10 or 11 for small displays, 12 for large displays - * All 32x32-pixel GIFs tested work with 11, most work with 10 - */ -GifDecoder decoder; - -int num_files; - -void screenClearCallback(void) { - //matrix.fillScreen(matrix.color565(0,0,0)); -} - -void updateScreenCallback(void) { - //backgroundLayer.swapBuffers(); -} - -void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) { - matrix.drawPixelRGB888(x, y, red, green, blue); -} - -// Setup method runs once, when the sketch starts -void setup() { - - Serial.begin(115200); - Serial.println("Starting AnimatedGIFs Sketch"); - - // Start filesystem - Serial.println(" * Loading SPIFFS"); - if(!SPIFFS.begin()){ - Serial.println("SPIFFS Mount Failed"); - } - - decoder.setScreenClearCallback(screenClearCallback); - decoder.setUpdateScreenCallback(updateScreenCallback); - decoder.setDrawPixelCallback(drawPixelCallback); - - decoder.setFileSeekCallback(fileSeekCallback); - decoder.setFilePositionCallback(filePositionCallback); - decoder.setFileReadCallback(fileReadCallback); - decoder.setFileReadBlockCallback(fileReadBlockCallback); - - - - matrix.begin(); - - // Clear screen - matrix.fillScreen(matrix.color565(0,0,0)); - - // Determine how many animated GIF files exist - num_files = enumerateGIFFiles(GIF_DIRECTORY, false); - - if(num_files < 0) { - Serial.println("No gifs directory"); - while(1); - } - - if(!num_files) { - Serial.println("Empty gifs directory"); - while(1); - } -} - -int file_index = -1; -void loop() { - static unsigned long futureTime; - - // int index = -1; //random(num_files); - - if(futureTime < millis()) { - if (++file_index >= num_files) { - file_index = 0; - } - - if (openGifFilenameByIndex(GIF_DIRECTORY, file_index) >= 0) { - // Can clear screen for new animation here, but this might cause flicker with short animations - // matrix.fillScreen(COLOR_BLACK); - // matrix.swapBuffers(); - - decoder.startDecoding(); - - // Calculate time in the future to terminate animation - futureTime = millis() + (DISPLAY_TIME_SECONDS * 1000); - } - } - - decoder.decodeFrame(); -} diff --git a/examples/AnimatedGIF/FSBrowser.h b/examples/AnimatedGIF/FSBrowser.h deleted file mode 100644 index c380226..0000000 --- a/examples/AnimatedGIF/FSBrowser.h +++ /dev/null @@ -1,179 +0,0 @@ -// ADAPTED FROM: https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino - -#define FILESYSTEM SPIFFS -#define FORMAT_FILESYSTEM true -#define DBG_OUTPUT_PORT Serial - - -//holds the current upload -File fsUploadFile; - -//format bytes -String formatBytes(size_t bytes) { - if (bytes < 1024) { - return String(bytes) + "B"; - } else if (bytes < (1024 * 1024)) { - return String(bytes / 1024.0) + "KB"; - } else if (bytes < (1024 * 1024 * 1024)) { - return String(bytes / 1024.0 / 1024.0) + "MB"; - } else { - return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; - } -} - -String getContentType(String filename) { - if (webServer.hasArg("download")) { - return "application/octet-stream"; - } else if (filename.endsWith(".htm")) { - return "text/html"; - } else if (filename.endsWith(".html")) { - return "text/html"; - } else if (filename.endsWith(".css")) { - return "text/css"; - } else if (filename.endsWith(".js")) { - return "application/javascript"; - } else if (filename.endsWith(".png")) { - return "image/png"; - } else if (filename.endsWith(".gif")) { - return "image/gif"; - } else if (filename.endsWith(".jpg")) { - return "image/jpeg"; - } else if (filename.endsWith(".ico")) { - return "image/x-icon"; - } else if (filename.endsWith(".xml")) { - return "text/xml"; - } else if (filename.endsWith(".pdf")) { - return "application/x-pdf"; - } else if (filename.endsWith(".zip")) { - return "application/x-zip"; - } else if (filename.endsWith(".gz")) { - return "application/x-gzip"; - } - return "text/plain"; -} - -bool exists(String path){ - bool yes = false; - File file = FILESYSTEM.open(path, "r"); - if(!file.isDirectory()){ - yes = true; - } - file.close(); - return yes; -} - -bool handleFileRead(String path) { - DBG_OUTPUT_PORT.println("handleFileRead: " + path); - if (path.endsWith("/")) { - path += "index.htm"; - } - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - if (exists(pathWithGz) || exists(path)) { - if (exists(pathWithGz)) { - path += ".gz"; - } - File file = FILESYSTEM.open(path, "r"); - webServer.streamFile(file, contentType); - file.close(); - return true; - } - return false; -} - -void handleFileUpload() { - if (webServer.uri() != "/edit") { - return; - } - HTTPUpload& upload = webServer.upload(); - if (upload.status == UPLOAD_FILE_START) { - String filename = upload.filename; - if (!filename.startsWith("/")) { - filename = "/" + filename; - } - DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); - fsUploadFile = FILESYSTEM.open(filename, "w"); - filename = String(); - } else if (upload.status == UPLOAD_FILE_WRITE) { - //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); - if (fsUploadFile) { - fsUploadFile.write(upload.buf, upload.currentSize); - } - } else if (upload.status == UPLOAD_FILE_END) { - if (fsUploadFile) { - fsUploadFile.close(); - } - DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); - } -} - -void handleFileDelete() { - if (webServer.args() == 0) { - return webServer.send(500, "text/plain", "BAD ARGS"); - } - String path = webServer.arg(0); - DBG_OUTPUT_PORT.println("handleFileDelete: " + path); - if (path == "/") { - return webServer.send(500, "text/plain", "BAD PATH"); - } - if (!exists(path)) { - return webServer.send(404, "text/plain", "FileNotFound"); - } - FILESYSTEM.remove(path); - webServer.send(200, "text/plain", ""); - path = String(); -} - -void handleFileCreate() { - if (webServer.args() == 0) { - return webServer.send(500, "text/plain", "BAD ARGS"); - } - String path = webServer.arg(0); - DBG_OUTPUT_PORT.println("handleFileCreate: " + path); - if (path == "/") { - return webServer.send(500, "text/plain", "BAD PATH"); - } - if (exists(path)) { - return webServer.send(500, "text/plain", "FILE EXISTS"); - } - File file = FILESYSTEM.open(path, "w"); - if (file) { - file.close(); - } else { - return webServer.send(500, "text/plain", "CREATE FAILED"); - } - webServer.send(200, "text/plain", ""); - path = String(); -} - -void handleFileList() { - if (!webServer.hasArg("dir")) { - webServer.send(500, "text/plain", "BAD ARGS"); - return; - } - - String path = webServer.arg("dir"); - DBG_OUTPUT_PORT.println("handleFileList: " + path); - - - File root = FILESYSTEM.open(path); - path = String(); - - String output = "["; - if(root.isDirectory()){ - File file = root.openNextFile(); - while(file){ - if (output != "[") { - output += ','; - } - output += "{\"type\":\""; - output += (file.isDirectory()) ? "dir" : "file"; - output += "\",\"name\":\""; - output += String(file.name()).substring(1); - output += "\"}"; - file = root.openNextFile(); - } - } - output += "]"; - webServer.send(200, "text/json", output); -} diff --git a/examples/AnimatedGIF/FilenameFunctions.cpp b/examples/AnimatedGIF/FilenameFunctions.cpp deleted file mode 100644 index e8a8f04..0000000 --- a/examples/AnimatedGIF/FilenameFunctions.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels - * - * This file contains code to enumerate and select animated GIF files by name - * - * Written by: Craig A. Lindley - * - * Other references: https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino - * - */ - -#include "FilenameFunctions.h" - -File file; - -int numberOfFiles; - -bool fileSeekCallback(unsigned long position) { - return file.seek(position); -} - -unsigned long filePositionCallback(void) { - return file.position(); -} - -int fileReadCallback(void) { - return file.read(); -} - -int fileReadBlockCallback(void * buffer, int numberOfBytes) { - return file.read((uint8_t*)buffer, numberOfBytes); -} - -bool isAnimationFile(const char filename []) { - String filenameString(filename); - -#if defined(ESP32) - // ESP32 filename includes the full path, so need to remove the path before looking at the filename - int pathindex = filenameString.lastIndexOf("/"); - if(pathindex >= 0) - filenameString.remove(0, pathindex + 1); -#endif - - DBG_OUTPUT_PORT.print(filenameString); - - if ((filenameString[0] == '_') || (filenameString[0] == '~') || (filenameString[0] == '.')) { - DBG_OUTPUT_PORT.println(" ignoring: leading _/~/. character"); - return false; - } - - filenameString.toUpperCase(); - if (filenameString.endsWith(".GIF") != 1) { - DBG_OUTPUT_PORT.println(" ignoring: doesn't end of .GIF"); - return false; - } - - DBG_OUTPUT_PORT.println(); - - return true; -} - -// Enumerate and possibly display the animated GIF filenames in GIFS directory -int enumerateGIFFiles(const char *directoryName, boolean displayFilenames) { - - numberOfFiles = 0; - - File directory = FILESYSTEM.open(directoryName); - if (!directory) { - return -1; - } - - File file = directory.openNextFile(); - while (file) { - if (isAnimationFile(file.name())) { - numberOfFiles++; - if (displayFilenames) { - DBG_OUTPUT_PORT.println(file.name()); - } - } - file.close(); - file = directory.openNextFile(); - } - - file.close(); - directory.close(); - - return numberOfFiles; -} - -// Get the full path/filename of the GIF file with specified index -void getGIFFilenameByIndex(const char *directoryName, int index, char *pnBuffer) { - - char* filename; - - // Make sure index is in range - if ((index < 0) || (index >= numberOfFiles)) - return; - - File directory = FILESYSTEM.open(directoryName); - if (!directory) - return; - - File file = directory.openNextFile(); - while (file && (index >= 0)) { - filename = (char*)file.name(); - - if (isAnimationFile(file.name())) { - index--; - -#if !defined(ESP32) - // Copy the directory name into the pathname buffer - ESP32 SD Library includes the full path name in the filename, so no need to add the directory name - strcpy(pnBuffer, directoryName); - // Append the filename to the pathname - strcat(pnBuffer, filename); -#else - strcpy(pnBuffer, filename); -#endif - } - - file.close(); - file = directory.openNextFile(); - } - - file.close(); - directory.close(); -} - -int openGifFilenameByIndex(const char *directoryName, int index) { - char pathname[30]; // long filename will break this... Smash the stack! i.e: - /* - * Stack smashing protect failure! - * - * abort() was called at PC 0x400d9a90 on core 1 - * - * Backtrace: 0x40088578:0x3ffb1ec0 0x4008877b:0x3ffb1ee0 0x400d9a90:0x3ffb1f00 0x400d1d62:0x3ffb1f20 0x400d182f:0x3ffb1f80 0x400ef86e:0x3ffb1fa0 - * - * Rebooting... - - */ - - getGIFFilenameByIndex(directoryName, index, pathname); - - DBG_OUTPUT_PORT.print("Pathname: "); - DBG_OUTPUT_PORT.println(pathname); - - if(file) - { - file.close(); - DBG_OUTPUT_PORT.print("Closing old file..."); - } - - // Attempt to open the file for reading - DBG_OUTPUT_PORT.print("Opening new file..."); - file = FILESYSTEM.open(pathname); - if (!file) { - DBG_OUTPUT_PORT.println("Error opening GIF file"); - return -1; - } - - return 0; -} - - -// Return a random animated gif path/filename from the specified directory -void chooseRandomGIFFilename(const char *directoryName, char *pnBuffer) { - - int index = random(numberOfFiles); - getGIFFilenameByIndex(directoryName, index, pnBuffer); -} diff --git a/examples/AnimatedGIF/FilenameFunctions.h b/examples/AnimatedGIF/FilenameFunctions.h deleted file mode 100644 index 2c92c10..0000000 --- a/examples/AnimatedGIF/FilenameFunctions.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef FILENAME_FUNCTIONS_H -#define FILENAME_FUNCTIONS_H - -#include "FS.h" // for the 'File' class -#include - - -#define FILESYSTEM SPIFFS -#define DBG_OUTPUT_PORT Serial - - -int enumerateGIFFiles(const char *directoryName, boolean displayFilenames); -void getGIFFilenameByIndex(const char *directoryName, int index, char *pnBuffer); -int openGifFilenameByIndex(const char *directoryName, int index); - -bool fileSeekCallback(unsigned long position); -unsigned long filePositionCallback(void); -int fileReadCallback(void); -int fileReadBlockCallback(void * buffer, int numberOfBytes); - -#endif diff --git a/examples/AnimatedGIF/GifDecoder.h b/examples/AnimatedGIF/GifDecoder.h deleted file mode 100644 index 8a3d14a..0000000 --- a/examples/AnimatedGIF/GifDecoder.h +++ /dev/null @@ -1,148 +0,0 @@ -#ifndef _GIFDECODER_H_ -#define _GIFDECODER_H_ - -#include -#include - - -typedef void (*callback)(void); -typedef void (*pixel_callback)(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue); -typedef void* (*get_buffer_callback)(void); - -typedef bool (*file_seek_callback)(unsigned long position); -typedef unsigned long (*file_position_callback)(void); -typedef int (*file_read_callback)(void); -typedef int (*file_read_block_callback)(void * buffer, int numberOfBytes); - -// LZW constants -// NOTE: LZW_MAXBITS should be set to 10 or 11 for small displays, 12 for large displays -// all 32x32-pixel GIFs tested work with 11, most work with 10 -// LZW_MAXBITS = 12 will support all GIFs, but takes 16kB RAM -#define LZW_SIZTABLE (1 << lzwMaxBits) - -template -class GifDecoder { -public: - int startDecoding(void); - int decodeFrame(void); - - void setScreenClearCallback(callback f); - void setUpdateScreenCallback(callback f); - void setDrawPixelCallback(pixel_callback f); - void setStartDrawingCallback(callback f); - - void setFileSeekCallback(file_seek_callback f); - void setFilePositionCallback(file_position_callback f); - void setFileReadCallback(file_read_callback f); - void setFileReadBlockCallback(file_read_block_callback f); - -private: - void parseTableBasedImage(void); - void decompressAndDisplayFrame(unsigned long filePositionAfter); - int parseData(void); - int parseGIFFileTerminator(void); - void parseCommentExtension(void); - void parseApplicationExtension(void); - void parseGraphicControlExtension(void); - void parsePlainTextExtension(void); - void parseGlobalColorTable(void); - void parseLogicalScreenDescriptor(void); - bool parseGifHeader(void); - void copyImageDataRect(uint8_t *dst, uint8_t *src, int x, int y, int width, int height); - void fillImageData(uint8_t colorIndex); - void fillImageDataRect(uint8_t colorIndex, int x, int y, int width, int height); - int readIntoBuffer(void *buffer, int numberOfBytes); - int readWord(void); - void backUpStream(int n); - int readByte(void); - - void lzw_decode_init(int csize); - int lzw_decode(uint8_t *buf, int len, uint8_t *bufend); - void lzw_setTempBuffer(uint8_t * tempBuffer); - int lzw_get_code(void); - - // Logical screen descriptor attributes - int lsdWidth; - int lsdHeight; - int lsdPackedField; - int lsdAspectRatio; - int lsdBackgroundIndex; - - // Table based image attributes - int tbiImageX; - int tbiImageY; - int tbiWidth; - int tbiHeight; - int tbiPackedBits; - bool tbiInterlaced; - - int frameDelay; - int transparentColorIndex; - int prevBackgroundIndex; - int prevDisposalMethod; - int disposalMethod; - int lzwCodeSize; - bool keyFrame; - int rectX; - int rectY; - int rectWidth; - int rectHeight; - - unsigned long nextFrameTime_ms; - - int colorCount; - rgb_24 palette[256]; - - char tempBuffer[260]; - - // Buffer image data is decoded into - uint8_t imageData[maxGifWidth * maxGifHeight]; - - // Backup image data buffer for saving portions of image disposal method == 3 - uint8_t imageDataBU[maxGifWidth * maxGifHeight]; - - callback screenClearCallback; - callback updateScreenCallback; - pixel_callback drawPixelCallback; - callback startDrawingCallback; - file_seek_callback fileSeekCallback; - file_position_callback filePositionCallback; - file_read_callback fileReadCallback; - file_read_block_callback fileReadBlockCallback; - - // LZW variables - int bbits; - int bbuf; - int cursize; // The current code size - int curmask; - int codesize; - int clear_code; - int end_code; - int newcodes; // First available code - int top_slot; // Highest code for current size - int extra_slot; - int slot; // Last read code - int fc, oc; - int bs; // Current buffer size for GIF - int bcnt; - uint8_t *sp; - uint8_t * temp_buffer; - - uint8_t stack [LZW_SIZTABLE]; - uint8_t suffix [LZW_SIZTABLE]; - uint16_t prefix [LZW_SIZTABLE]; - - // Masks for 0 .. 16 bits - unsigned int mask[17] = { - 0x0000, 0x0001, 0x0003, 0x0007, - 0x000F, 0x001F, 0x003F, 0x007F, - 0x00FF, 0x01FF, 0x03FF, 0x07FF, - 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, - 0xFFFF - }; -}; - -#include "GifDecoder_Impl.h" -#include "LzwDecoder_Impl.h" - -#endif diff --git a/examples/AnimatedGIF/GifDecoder_Impl.h b/examples/AnimatedGIF/GifDecoder_Impl.h deleted file mode 100644 index c7c5a5d..0000000 --- a/examples/AnimatedGIF/GifDecoder_Impl.h +++ /dev/null @@ -1,806 +0,0 @@ -/* - * This file contains code to parse animated GIF files - * - * Written by: Craig A. Lindley - * - * Copyright (c) 2014 Craig A. Lindley - * Minor modifications by Louis Beaudoin (pixelmatix) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#define GIFDEBUG 0 - -// This file contains C code, and ESP32 Arduino has changed to use the C++ template version of min()/max() which we can't use with C, so we can't depend on a #define min() from Arduino anymore -#ifndef min -#define min(a,b) ((a)<(b)?(a):(b)) -#endif - -#include "GifDecoder.h" - -#if GIFDEBUG == 1 -#define DEBUG_SCREEN_DESCRIPTOR 1 -#define DEBUG_GLOBAL_COLOR_TABLE 1 -#define DEBUG_PROCESSING_PLAIN_TEXT_EXT 1 -#define DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT 1 -#define DEBUG_PROCESSING_APP_EXT 1 -#define DEBUG_PROCESSING_COMMENT_EXT 1 -#define DEBUG_PROCESSING_FILE_TERM 1 -#define DEBUG_PROCESSING_TABLE_IMAGE_DESC 1 -#define DEBUG_PROCESSING_TBI_DESC_START 1 -#define DEBUG_PROCESSING_TBI_DESC_INTERLACED 1 -#define DEBUG_PROCESSING_TBI_DESC_LOCAL_COLOR_TABLE 1 -#define DEBUG_PROCESSING_TBI_DESC_LZWCODESIZE 1 -#define DEBUG_PROCESSING_TBI_DESC_DATABLOCKSIZE 1 -#define DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_OVERFLOW 1 -#define DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_SIZE 1 -#define DEBUG_PARSING_DATA 1 -#define DEBUG_DECOMPRESS_AND_DISPLAY 1 - -#define DEBUG_WAIT_FOR_KEY_PRESS 0 - -#endif - -#include "GifDecoder.h" - - -// Error codes -#define ERROR_NONE 0 -#define ERROR_DONE_PARSING 1 -#define ERROR_WAITING 2 -#define ERROR_FILEOPEN -1 -#define ERROR_FILENOTGIF -2 -#define ERROR_BADGIFFORMAT -3 -#define ERROR_UNKNOWNCONTROLEXT -4 - -#define GIFHDRTAGNORM "GIF87a" // tag in valid GIF file -#define GIFHDRTAGNORM1 "GIF89a" // tag in valid GIF file -#define GIFHDRSIZE 6 - -// Global GIF specific definitions -#define COLORTBLFLAG 0x80 -#define INTERLACEFLAG 0x40 -#define TRANSPARENTFLAG 0x01 - -#define NO_TRANSPARENT_INDEX -1 - -// Disposal methods -#define DISPOSAL_NONE 0 -#define DISPOSAL_LEAVE 1 -#define DISPOSAL_BACKGROUND 2 -#define DISPOSAL_RESTORE 3 - - - -template -void GifDecoder::setStartDrawingCallback(callback f) { - startDrawingCallback = f; -} - -template -void GifDecoder::setUpdateScreenCallback(callback f) { - updateScreenCallback = f; -} - -template -void GifDecoder::setDrawPixelCallback(pixel_callback f) { - drawPixelCallback = f; -} - -template -void GifDecoder::setScreenClearCallback(callback f) { - screenClearCallback = f; -} - -template -void GifDecoder::setFileSeekCallback(file_seek_callback f) { - fileSeekCallback = f; -} - -template -void GifDecoder::setFilePositionCallback(file_position_callback f) { - filePositionCallback = f; -} - -template -void GifDecoder::setFileReadCallback(file_read_callback f) { - fileReadCallback = f; -} - -template -void GifDecoder::setFileReadBlockCallback(file_read_block_callback f) { - fileReadBlockCallback = f; -} - -// Backup the read stream by n bytes -template -void GifDecoder::backUpStream(int n) { - fileSeekCallback(filePositionCallback() - n); -} - -// Read a file byte -template -int GifDecoder::readByte() { - - int b = fileReadCallback(); - if (b == -1) { -#if GIFDEBUG == 1 - Serial.println("Read error or EOF occurred"); -#endif - } - return b; -} - -// Read a file word -template -int GifDecoder::readWord() { - - int b0 = readByte(); - int b1 = readByte(); - return (b1 << 8) | b0; -} - -// Read the specified number of bytes into the specified buffer -template -int GifDecoder::readIntoBuffer(void *buffer, int numberOfBytes) { - - int result = fileReadBlockCallback(buffer, numberOfBytes); - if (result == -1) { - Serial.println("Read error or EOF occurred"); - } - return result; -} - -// Fill a portion of imageData buffer with a color index -template -void GifDecoder::fillImageDataRect(uint8_t colorIndex, int x, int y, int width, int height) { - - int yOffset; - - for (int yy = y; yy < height + y; yy++) { - yOffset = yy * maxGifWidth; - for (int xx = x; xx < width + x; xx++) { - imageData[yOffset + xx] = colorIndex; - } - } -} - -// Fill entire imageData buffer with a color index -template -void GifDecoder::fillImageData(uint8_t colorIndex) { - - memset(imageData, colorIndex, sizeof(imageData)); -} - -// Copy image data in rect from a src to a dst -template -void GifDecoder::copyImageDataRect(uint8_t *dst, uint8_t *src, int x, int y, int width, int height) { - - int yOffset, offset; - - for (int yy = y; yy < height + y; yy++) { - yOffset = yy * maxGifWidth; - for (int xx = x; xx < width + x; xx++) { - offset = yOffset + xx; - dst[offset] = src[offset]; - } - } -} - -// Make sure the file is a Gif file -template -bool GifDecoder::parseGifHeader() { - - char buffer[10]; - - readIntoBuffer(buffer, GIFHDRSIZE); - if ((strncmp(buffer, GIFHDRTAGNORM, GIFHDRSIZE) != 0) && - (strncmp(buffer, GIFHDRTAGNORM1, GIFHDRSIZE) != 0)) { - return false; - } - else { - return true; - } -} - -// Parse the logical screen descriptor -template -void GifDecoder::parseLogicalScreenDescriptor() { - - lsdWidth = readWord(); - lsdHeight = readWord(); - lsdPackedField = readByte(); - lsdBackgroundIndex = readByte(); - lsdAspectRatio = readByte(); - -#if GIFDEBUG == 1 && DEBUG_SCREEN_DESCRIPTOR == 1 - Serial.print("lsdWidth: "); - Serial.println(lsdWidth); - Serial.print("lsdHeight: "); - Serial.println(lsdHeight); - Serial.print("lsdPackedField: "); - Serial.println(lsdPackedField, HEX); - Serial.print("lsdBackgroundIndex: "); - Serial.println(lsdBackgroundIndex); - Serial.print("lsdAspectRatio: "); - Serial.println(lsdAspectRatio); -#endif -} - -// Parse the global color table -template -void GifDecoder::parseGlobalColorTable() { - - // Does a global color table exist? - if (lsdPackedField & COLORTBLFLAG) { - - // A GCT was present determine how many colors it contains - colorCount = 1 << ((lsdPackedField & 7) + 1); - -#if GIFDEBUG == 1 && DEBUG_GLOBAL_COLOR_TABLE == 1 - Serial.print("Global color table with "); - Serial.print(colorCount); - Serial.println(" colors present"); -#endif - // Read color values into the palette array - int colorTableBytes = sizeof(rgb_24) * colorCount; - readIntoBuffer(palette, colorTableBytes); - } -} - -// Parse plain text extension and dispose of it -template -void GifDecoder::parsePlainTextExtension() { - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_PLAIN_TEXT_EXT == 1 - Serial.println("\nProcessing Plain Text Extension"); -#endif - // Read plain text header length - uint8_t len = readByte(); - - // Consume plain text header data - readIntoBuffer(tempBuffer, len); - - // Consume the plain text data in blocks - len = readByte(); - while (len != 0) { - readIntoBuffer(tempBuffer, len); - len = readByte(); - } -} - -// Parse a graphic control extension -template -void GifDecoder::parseGraphicControlExtension() { - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT == 1 - Serial.println("\nProcessing Graphic Control Extension"); -#endif - int len = readByte(); // Check length - if (len != 4) { - Serial.println("Bad graphic control extension"); - } - - int packedBits = readByte(); - frameDelay = readWord(); - transparentColorIndex = readByte(); - - if ((packedBits & TRANSPARENTFLAG) == 0) { - // Indicate no transparent index - transparentColorIndex = NO_TRANSPARENT_INDEX; - } - disposalMethod = (packedBits >> 2) & 7; - if (disposalMethod > 3) { - disposalMethod = 0; - Serial.println("Invalid disposal value"); - } - - readByte(); // Toss block end - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT == 1 - Serial.print("PacketBits: "); - Serial.println(packedBits, HEX); - Serial.print("Frame delay: "); - Serial.println(frameDelay); - Serial.print("transparentColorIndex: "); - Serial.println(transparentColorIndex); - Serial.print("disposalMethod: "); - Serial.println(disposalMethod); -#endif -} - -// Parse application extension -template -void GifDecoder::parseApplicationExtension() { - - memset(tempBuffer, 0, sizeof(tempBuffer)); - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_APP_EXT == 1 - Serial.println("\nProcessing Application Extension"); -#endif - - // Read block length - uint8_t len = readByte(); - - // Read app data - readIntoBuffer(tempBuffer, len); - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_APP_EXT == 1 - // Conditionally display the application extension string - if (strlen(tempBuffer) != 0) { - Serial.print("Application Extension: "); - Serial.println(tempBuffer); - } -#endif - - // Consume any additional app data - len = readByte(); - while (len != 0) { - readIntoBuffer(tempBuffer, len); - len = readByte(); - } -} - -// Parse comment extension -template -void GifDecoder::parseCommentExtension() { - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_COMMENT_EXT == 1 - Serial.println("\nProcessing Comment Extension"); -#endif - - // Read block length - uint8_t len = readByte(); - while (len != 0) { - // Clear buffer - memset(tempBuffer, 0, sizeof(tempBuffer)); - - // Read len bytes into buffer - readIntoBuffer(tempBuffer, len); - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_COMMENT_EXT == 1 - // Display the comment extension string - if (strlen(tempBuffer) != 0) { - Serial.print("Comment Extension: "); - Serial.println(tempBuffer); - } -#endif - // Read the new block length - len = readByte(); - } -} - -// Parse file terminator -template -int GifDecoder::parseGIFFileTerminator() { - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_FILE_TERM == 1 - Serial.println("\nProcessing file terminator"); -#endif - - uint8_t b = readByte(); - if (b != 0x3B) { - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_FILE_TERM == 1 - Serial.print("Terminator byte: "); - Serial.println(b, HEX); -#endif - Serial.println("Bad GIF file format - Bad terminator"); - return ERROR_BADGIFFORMAT; - } - else { - return ERROR_NONE; - } -} - -// Parse table based image data -template -void GifDecoder::parseTableBasedImage() { - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_START == 1 - Serial.println("\nProcessing Table Based Image Descriptor"); -#endif - -#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 - Serial.println("File Position: "); - Serial.println(filePositionCallback()); - Serial.println("File Size: "); - //Serial.println(file.size()); -#endif - - // Parse image descriptor - tbiImageX = readWord(); - tbiImageY = readWord(); - tbiWidth = readWord(); - tbiHeight = readWord(); - tbiPackedBits = readByte(); - -#if GIFDEBUG == 1 - Serial.print("tbiImageX: "); - Serial.println(tbiImageX); - Serial.print("tbiImageY: "); - Serial.println(tbiImageY); - Serial.print("tbiWidth: "); - Serial.println(tbiWidth); - Serial.print("tbiHeight: "); - Serial.println(tbiHeight); - Serial.print("PackedBits: "); - Serial.println(tbiPackedBits, HEX); -#endif - - // Is this image interlaced ? - tbiInterlaced = ((tbiPackedBits & INTERLACEFLAG) != 0); - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_INTERLACED == 1 - Serial.print("Image interlaced: "); - Serial.println((tbiInterlaced != 0) ? "Yes" : "No"); -#endif - - // Does this image have a local color table ? - bool localColorTable = ((tbiPackedBits & COLORTBLFLAG) != 0); - - if (localColorTable) { - int colorBits = ((tbiPackedBits & 7) + 1); - colorCount = 1 << colorBits; - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LOCAL_COLOR_TABLE == 1 - Serial.print("Local color table with "); - Serial.print(colorCount); - Serial.println(" colors present"); -#endif - // Read colors into palette - int colorTableBytes = sizeof(rgb_24) * colorCount; - readIntoBuffer(palette, colorTableBytes); - } - - // One time initialization of imageData before first frame - if (keyFrame) { - if (transparentColorIndex == NO_TRANSPARENT_INDEX) { - fillImageData(lsdBackgroundIndex); - } - else { - fillImageData(transparentColorIndex); - } - keyFrame = false; - - rectX = 0; - rectY = 0; - rectWidth = maxGifWidth; - rectHeight = maxGifHeight; - } - // Don't clear matrix screen for these disposal methods - if ((prevDisposalMethod != DISPOSAL_NONE) && (prevDisposalMethod != DISPOSAL_LEAVE)) { - if(screenClearCallback) - (*screenClearCallback)(); - } - - // Process previous disposal method - if (prevDisposalMethod == DISPOSAL_BACKGROUND) { - // Fill portion of imageData with previous background color - fillImageDataRect(prevBackgroundIndex, rectX, rectY, rectWidth, rectHeight); - } - else if (prevDisposalMethod == DISPOSAL_RESTORE) { - copyImageDataRect(imageData, imageDataBU, rectX, rectY, rectWidth, rectHeight); - } - - // Save disposal method for this frame for next time - prevDisposalMethod = disposalMethod; - - if (disposalMethod != DISPOSAL_NONE) { - // Save dimensions of this frame - rectX = tbiImageX; - rectY = tbiImageY; - rectWidth = tbiWidth; - rectHeight = tbiHeight; - - // limit rectangle to the bounds of maxGifWidth*maxGifHeight - if(rectX + rectWidth > maxGifWidth) - rectWidth = maxGifWidth-rectX; - if(rectY + rectHeight > maxGifHeight) - rectHeight = maxGifHeight-rectY; - if(rectX >= maxGifWidth || rectY >= maxGifHeight) { - rectX = rectY = rectWidth = rectHeight = 0; - } - - if (disposalMethod == DISPOSAL_BACKGROUND) { - if (transparentColorIndex != NO_TRANSPARENT_INDEX) { - prevBackgroundIndex = transparentColorIndex; - } - else { - prevBackgroundIndex = lsdBackgroundIndex; - } - } - else if (disposalMethod == DISPOSAL_RESTORE) { - copyImageDataRect(imageDataBU, imageData, rectX, rectY, rectWidth, rectHeight); - } - } - - // Read the min LZW code size - lzwCodeSize = readByte(); - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LZWCODESIZE == 1 - Serial.print("LzwCodeSize: "); - Serial.println(lzwCodeSize); - Serial.println("File Position Before: "); - Serial.println(filePositionCallback()); -#endif - - unsigned long filePositionBefore = filePositionCallback(); - - // Gather the lzw image data - // NOTE: the dataBlockSize byte is left in the data as the lzw decoder needs it - int offset = 0; - int dataBlockSize = readByte(); - while (dataBlockSize != 0) { -#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_DATABLOCKSIZE == 1 - Serial.print("dataBlockSize: "); - Serial.println(dataBlockSize); -#endif - backUpStream(1); - dataBlockSize++; - fileSeekCallback(filePositionCallback() + dataBlockSize); - - offset += dataBlockSize; - dataBlockSize = readByte(); - } - -#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_SIZE == 1 - Serial.print("total lzwImageData Size: "); - Serial.println(offset); - Serial.println("File Position Test: "); - Serial.println(filePositionCallback()); -#endif - - // this is the position where GIF decoding needs to pick up after decompressing frame - unsigned long filePositionAfter = filePositionCallback(); - - fileSeekCallback(filePositionBefore); - - // Process the animation frame for display - - // Initialize the LZW decoder for this frame - lzw_decode_init(lzwCodeSize); - lzw_setTempBuffer((uint8_t*)tempBuffer); - - // Make sure there is at least some delay between frames - if (frameDelay < 1) { - frameDelay = 1; - } - - // Decompress LZW data and display the frame - decompressAndDisplayFrame(filePositionAfter); - - // Graphic control extension is for a single frame - transparentColorIndex = NO_TRANSPARENT_INDEX; - disposalMethod = DISPOSAL_NONE; -} - -// Parse gif data -template -int GifDecoder::parseData() { - if(nextFrameTime_ms > millis()) - return ERROR_WAITING; - -#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 - Serial.println("\nParsing Data Block"); -#endif - - bool parsedFrame = false; - while (!parsedFrame) { - -#if GIFDEBUG == 1 && DEBUG_WAIT_FOR_KEY_PRESS == 1 - Serial.println("\nPress Key For Next"); - while(Serial.read() <= 0); -#endif - - // Determine what kind of data to process - uint8_t b = readByte(); - - if (b == 0x2c) { - // Parse table based image -#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 - Serial.println("\nParsing Table Based"); -#endif - parseTableBasedImage(); - parsedFrame = true; - - } - else if (b == 0x21) { - // Parse extension - b = readByte(); - -#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 - Serial.println("\nParsing Extension"); -#endif - - // Determine which kind of extension to parse - switch (b) { - case 0x01: - // Plain test extension - parsePlainTextExtension(); - break; - case 0xf9: - // Graphic control extension - parseGraphicControlExtension(); - break; - case 0xfe: - // Comment extension - parseCommentExtension(); - break; - case 0xff: - // Application extension - parseApplicationExtension(); - break; - default: - Serial.print("Unknown control extension: "); - Serial.println(b, HEX); - return ERROR_UNKNOWNCONTROLEXT; - } - } - else { -#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 - Serial.println("\nParsing Done"); -#endif - - // Push unprocessed byte back into the stream for later processing - backUpStream(1); - - return ERROR_DONE_PARSING; - } - } - return ERROR_NONE; -} - -template -int GifDecoder::startDecoding(void) { - // Initialize variables - keyFrame = true; - prevDisposalMethod = DISPOSAL_NONE; - transparentColorIndex = NO_TRANSPARENT_INDEX; - nextFrameTime_ms = 0; - fileSeekCallback(0); - - // Validate the header - if (! parseGifHeader()) { - Serial.println("Not a GIF file"); - return ERROR_FILENOTGIF; - } - // If we get here we have a gif file to process - - // Parse the logical screen descriptor - parseLogicalScreenDescriptor(); - - // Parse the global color table - parseGlobalColorTable(); - - return ERROR_NONE; -} - -template -int GifDecoder::decodeFrame(void) { - // Parse gif data - int result = parseData(); - if (result < ERROR_NONE) { - Serial.println("Error: "); - Serial.println(result); - Serial.println(" occurred during parsing of data"); - return result; - } - - if (result == ERROR_DONE_PARSING) { - //startDecoding(); - // Initialize variables like with a new file - keyFrame = true; - prevDisposalMethod = DISPOSAL_NONE; - transparentColorIndex = NO_TRANSPARENT_INDEX; - nextFrameTime_ms = 0; - fileSeekCallback(0); - - // parse Gif Header like with a new file - parseGifHeader(); - - // Parse the logical screen descriptor - parseLogicalScreenDescriptor(); - - // Parse the global color table - parseGlobalColorTable(); - } - - return result; -} - -// Decompress LZW data and display animation frame -template -void GifDecoder::decompressAndDisplayFrame(unsigned long filePositionAfter) { - - // Each pixel of image is 8 bits and is an index into the palette - - // How the image is decoded depends upon whether it is interlaced or not - // Decode the interlaced LZW data into the image buffer - if (tbiInterlaced) { - // Decode every 8th line starting at line 0 - for (int line = tbiImageY + 0; line < tbiHeight + tbiImageY; line += 8) { - lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); - } - // Decode every 8th line starting at line 4 - for (int line = tbiImageY + 4; line < tbiHeight + tbiImageY; line += 8) { - lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); - } - // Decode every 4th line starting at line 2 - for (int line = tbiImageY + 2; line < tbiHeight + tbiImageY; line += 4) { - lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); - } - // Decode every 2nd line starting at line 1 - for (int line = tbiImageY + 1; line < tbiHeight + tbiImageY; line += 2) { - lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); - } - } - else { - // Decode the non interlaced LZW data into the image data buffer - for (int line = tbiImageY; line < tbiHeight + tbiImageY; line++) { - lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, imageData + sizeof(imageData)); - } - } - -#if GIFDEBUG == 1 && DEBUG_DECOMPRESS_AND_DISPLAY == 1 - Serial.println("File Position After: "); - Serial.println(filePositionCallback()); -#endif - -#if GIFDEBUG == 1 && DEBUG_WAIT_FOR_KEY_PRESS == 1 - Serial.println("\nPress Key For Next"); - while(Serial.read() <= 0); -#endif - - // LZW doesn't parse through all the data, manually set position - fileSeekCallback(filePositionAfter); - - // Optional callback can be used to get drawing routines ready - if(startDrawingCallback) - (*startDrawingCallback)(); - - // Image data is decompressed, now display portion of image affected by frame - int yOffset, pixel; - for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++) { - yOffset = y * maxGifWidth; - for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++) { - // Get the next pixel - pixel = imageData[yOffset + x]; - - // Check pixel transparency - if (pixel == transparentColorIndex) { - continue; - } - - // Pixel not transparent so get color from palette and draw the pixel - if(drawPixelCallback) - (*drawPixelCallback)(x, y, palette[pixel].red, palette[pixel].green, palette[pixel].blue); - } - } - // Make animation frame visible - // swapBuffers() call can take up to 1/framerate seconds to return (it waits until a buffer copy is complete) - // note the time before calling - - // wait until time to display next frame - while(nextFrameTime_ms > millis()); - - // calculate time to display next frame - nextFrameTime_ms = millis() + (10 * frameDelay); - if(updateScreenCallback) - (*updateScreenCallback)(); -} diff --git a/examples/AnimatedGIF/LICENSE.txt b/examples/AnimatedGIF/LICENSE.txt deleted file mode 100644 index c69f703..0000000 --- a/examples/AnimatedGIF/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Craig A. Lindley - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/examples/AnimatedGIF/LzwDecoder_Impl.h b/examples/AnimatedGIF/LzwDecoder_Impl.h deleted file mode 100644 index 720ac24..0000000 --- a/examples/AnimatedGIF/LzwDecoder_Impl.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This file contains code to decompress the LZW encoded animated GIF data - * - * Written by: Craig A. Lindley, Fabrice Bellard and Steven A. Bennett - * See my book, "Practical Image Processing in C", John Wiley & Sons, Inc. - * - * Copyright (c) 2014 Craig A. Lindley - * Minor modifications by Louis Beaudoin (pixelmatix) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#define LZWDEBUG 0 - -#include "GifDecoder.h" - -template -void GifDecoder::lzw_setTempBuffer(uint8_t * tempBuffer) { - temp_buffer = tempBuffer; -} - -// Initialize LZW decoder -// csize initial code size in bits -// buf input data -template -void GifDecoder::lzw_decode_init (int csize) { - - // Initialize read buffer variables - bbuf = 0; - bbits = 0; - bs = 0; - bcnt = 0; - - // Initialize decoder variables - codesize = csize; - cursize = codesize + 1; - curmask = mask[cursize]; - top_slot = 1 << cursize; - clear_code = 1 << codesize; - end_code = clear_code + 1; - slot = newcodes = clear_code + 2; - oc = fc = -1; - sp = stack; -} - -// Get one code of given number of bits from stream -template -int GifDecoder::lzw_get_code() { - - while (bbits < cursize) { - if (bcnt == bs) { - // get number of bytes in next block - readIntoBuffer(temp_buffer, 1); - bs = temp_buffer[0]; - readIntoBuffer(temp_buffer, bs); - bcnt = 0; - } - bbuf |= temp_buffer[bcnt] << bbits; - bbits += 8; - bcnt++; - } - int c = bbuf; - bbuf >>= cursize; - bbits -= cursize; - return c & curmask; -} - -// Decode given number of bytes -// buf 8 bit output buffer -// len number of pixels to decode -// returns the number of bytes decoded -template -int GifDecoder::lzw_decode(uint8_t *buf, int len, uint8_t *bufend) { - int l, c, code; - -#if LZWDEBUG == 1 - unsigned char debugMessagePrinted = 0; -#endif - - if (end_code < 0) { - return 0; - } - l = len; - - for (;;) { - while (sp > stack) { - // load buf with data if we're still within bounds - if(buf < bufend) { - *buf++ = *(--sp); - } else { - // out of bounds, keep incrementing the pointers, but don't use the data -#if LZWDEBUG == 1 - // only print this message once per call to lzw_decode - if(buf == bufend) - Serial.println("****** LZW imageData buffer overrun *******"); -#endif - } - if ((--l) == 0) { - return len; - } - } - c = lzw_get_code(); - if (c == end_code) { - break; - - } - else if (c == clear_code) { - cursize = codesize + 1; - curmask = mask[cursize]; - slot = newcodes; - top_slot = 1 << cursize; - fc= oc= -1; - - } - else { - - code = c; - if ((code == slot) && (fc >= 0)) { - *sp++ = fc; - code = oc; - } - else if (code >= slot) { - break; - } - while (code >= newcodes) { - *sp++ = suffix[code]; - code = prefix[code]; - } - *sp++ = code; - if ((slot < top_slot) && (oc >= 0)) { - suffix[slot] = code; - prefix[slot++] = oc; - } - fc = code; - oc = c; - if (slot >= top_slot) { - if (cursize < lzwMaxBits) { - top_slot <<= 1; - curmask = mask[++cursize]; - } else { -#if LZWDEBUG == 1 - if(!debugMessagePrinted) { - debugMessagePrinted = 1; - Serial.println("****** cursize >= lzwMaxBits *******"); - } -#endif - } - - } - } - } - end_code = -1; - return len - l; -} diff --git a/examples/AnimatedGIF/data/Text_Animations.txt.txt b/examples/AnimatedGIF/data/Text_Animations.txt.txt deleted file mode 100644 index 8431cd4..0000000 --- a/examples/AnimatedGIF/data/Text_Animations.txt.txt +++ /dev/null @@ -1 +0,0 @@ -https://loading.io/animation/text/ \ No newline at end of file diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel/AnimatedGIFPanel.ino b/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino similarity index 100% rename from examples/AnimatedGIFPanel/AnimatedGIFPanel/AnimatedGIFPanel.ino rename to examples/AnimatedGIFPanel/AnimatedGIFPanel.ino diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif b/examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif deleted file mode 100644 index 0a219a4621a542c454a4f96ec69c21ec4b6760be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44831 zcmbq)XCT`R`))$)y-BGgVvkZ3ZM8^{#Ez|~O|6=>R|K&o_TGDMN)@%0+NDG7((2Gw ztJCp3&-?z*htu=C=RB{EKIC^@_qwk8&M%s14Mine6#xYA1@On;_wN8^W&kxcAUz$h zwZ&Fl4Y08Rpir`dg95$10QrlOoSf|Y`v6_kH39+f{ym_fK>-E>C@BGMnrODR0{r~} zCno@1U4W@6U~UeO9eHDF3Q$_g*x3naZfI>-u)cnJtYNS> zf2yybaPX+NrslLuPVOe=)b~bzU*G8uHqM=zg9CQ4)0y>dEJFo!ue_xYZ~$z9{my+)bWuDM>jwzoKWWa(O+Xm#a^ z!R_0pCH|+crcYg6uaz^p9^x`TbsOPur{SJP+1XcSw@%k9x3<9G?>u~4qg(Gs6JJkP zf9W;eT6ytt$PayO-Okh=ZEvq2tFW_lx>{oqt)ddDqLLP39vpoA&1C*N_s~S=)3-j+ zhZCnrr0c<0?$Jn0Wo6eup4Erp;HlhT<7k}W=`Z{5zbv*kH%m{gni^FM3?hzyweO!w zN-7}k{=)wFW&G=xMP^2NaHhqD0iPy@19S+A^&pq<^`fFt8R19-!Ju!as$FI)l)Ax6sZWQFlfB04N`cy99^jquc zEi2b3&uF92(2&o+th+kfQ@TD3oW7mNep%dg!RGYs$myrP(=T18n`@^!d%uqEp9Tj< zpLCvnZvM`}$r{9GW8!Opjwr0j2dKs3=B5D!~7M;`$6D+oXiI0Z=j(LjH+5`at+pISTp zhN--u<4I!sdjEH(*ez7nUVPQ7sjjzl;~%sie@=e+ZT8DACVJYq3bNmP0y%>E?z3Og zQ|S78imzw-PHjX|9Bvi4)HS(_cWlu`Kls5C^M)sC{pcE_bPW%y%vd=^^~0q+E}jlp zp+)m*!=33j!L~HN`CO1G^Thx8nXd=D4xv6FORq{mftTU^>jzxyB*wuq13zlTY|U!c zZ?~@1LSM5_&-!?{1iws}nZ1gbsC-@NG46Q7>0@BUi}yXD z*DSgZntcKH$=pU9LWP12LU1BBmT{tb_ECufnrN*wF*RgnZeD&tVZrY}P&%nZdZ8r1 zsS_1GlkS&`=W6^gjsGr%``S9T^y>+j5@SxACeWU^NpDIE#TFrHfFrxljOrd{=2|Zk+E)?BfnD{5A zf-DP;GfdpW{o(g@M|{J~&ijbr%z|U%OoAj6&1h0hB}m3(#7t~*E;hbEAMaYM6Cn^) zjtL66Q-k)dZ$$rrs1;@B=+xbRZ{Q4*_HO~@{v9Aq)lzjKy}{)n2R>>`BQgEWq%2zS zn;%zhrs~oLMR|FI>0gKsG6(0rL(_9XEGHP3)%E-Y@GPw9iXc}&kf)efaJa5?bYciWCm}J~BSp)O zND|Ay;If1A&;^B_#i-J<3eBp*8jZT_2KB76;M^8uT3LD*q9;4KPc5=3CT7Xl0$ z1Nh>+eB2(k+EX}8{|l|oZ_yV2U2I2C5HHaVb~>+R_IQ!8_gYWk2AbmUf1H%jR=m-n zlL^L)g|S3x!|+L=gm?^HQgUjVrd>KdGz%4&n^d4#R2*EYQC{IyrCw7DtVcH1wjfxd zoANuzyNeui~t%s_FQ)=X|)*S~0Ec?T`>zTCxajnrM6ojd;8k9}SIc za&Rh9hbG(G-Z>8=%v4d39bT%Fo{<$?Q>#VZM$W|5)S}Jj(CFRVgDkHs9JsGmRaiW# zcGtepD|ZTzIO{z(6_u17OPB`)u7!j>M?jg}gS>q~{@X~qKBqSyK7Km>{Kew8G`jwd z20+*2s!7YBF)>-+u+}9?WoCt?wDL_r=>3-k?{r{6UZJ64@^>P&E_vc(V&W6fFa>+h z)KDS`lO&cCBJY%sfx(2CXtPtp%cC;0io6>dwIn3S$)Q1Q5gqBx^0KYK_5q}8b?t-D z(J{5cV(-#>0~syzz`U`-l=&yi(erUH#x`?iX4PDo_I&-94v=;Zi$|ZooP7QE{mXBu z)cp;WdvSR^P|mQ%I5`Ps{Z{f@H})m0y2(4b*R>;7z`;?s<)y=P-93YF(Xl#q3W-tj z$$F7uo{0{j8EEobRKk$~QB(!tb`cd-y5x=IcXBH0wQFjF+uAjXO9{B1USw7dE^iP~ zLkvzIL)=RsPCo>6O#<-?h?c;_{MF(o?%uwu>b9No&v#zEK78}`o%3(u|6M`#it5xG z*LR{`91c~|f}SX{9;7vyKs@q7Zq^zKQ%QzIGlRLqvE)=V(IL!n1T0+A1D{4T5S2<6 zbIvg!gC}Krh7}vY<;60^s;c!x13ep?^*QPqnmRG1ytkw8_MuqwqYGVz(diYQsgo%3 zvgDcUM~Fz=V$AXiARs7t=sCcx0Z8NHw~e%QaFm~T{Z~O*{gy`a-(;aXGt2DIJggO} z11YiSrp6a+XpeR{9RfV~=1}{FWZdUNV`L?jA`LHt0%P3Z35MKY!BilTWQb3dJRgy# zFO=-&5mr*FmyuN!UW+a8XlmBIoEcQzp(DhX(pYmBT@X_=IHX>mojb0Xl#)9A5J7hN zK`dcmA|NQlt$hu_>e_SL+jj$LYbP)7xc~0`(HV{3r=s(3ICN&Jxjn1X88FM*V(qCR zYod*Us2T>ybA8vL8o&xzd_FrTIX(YAmDbo>|ChN zno9J9gjX5x>p&m`0=(X!R8vbVZSHG@h_!e3 zBnvh5^>d8@g@%)rCdcmeDLqUQdNkSAJiQcu_i_Iku=-it`ith)=Ix!By_fr2Lx;Gx z?^<7f{Pb@1=xg-xZOZn)$yI$nE->k<1rvx&c#yL9%B^^F zSDUBgZ?B@{B4{-P0|Fpi?i3tX!nDa`akwBLZ+rrVJUQh~8gB;17LpU~$xPu`putg+ zlEzb2U0YY&(1@&QPKC6!t95pHwBAMZchwK76+I}c7*)$^&YMOg&3Y2krz4_r@UetB zgnxH%=sLp1HP`d1_l}0GJ%xPb;h&0fGWy$^tNt4v)p5uY6)D1i$%xZOnx5w+kE#Sz zwV2+80!FC#4)w_Gf`iYhj+QJ^8;8e-(j+8el2d}tOr@r6Rw{(XIZq?7uqdKTy`nOv zx&~2KAJo_cXlV^;Z|bTK?X4;3PAlzD%dE^9L5>wpOpZoBj60t@+nE&>zO3%*9^-S> z|4I3o%iDUnfAGf~{w?;ub0U?UUXA*ZVa;(5ga$nyFT{Q9(77G0;`H^Se*JfH26BpR<3D$fw-myoC(o8~Sbl#cPD%8ba((@wVaFDca`uVt&O)+rD3t7=3P6ot37 z1G?gJdOK`Wvw)<5zO?v(`@@m)Y?H{WfS!R{ZvMA#Jwn;pEk60niM;qN=D(Y`r9=%6 z%a5pm6}G62vv?;TBOZzo?~S*1hXLKff^+N4!e*U;IePG7=QmfqNpjL-Ed7y+b_#(|kr zfU#N6xv8}BM11T#qJ6mUIl$Gu!pCn*{Y*BT5B{3C?|#dn{%>lgkd3;+K-_R(g*nP5 zhhqKqgEPO1efIw9Gi;m*BsdDl%t0Tf!-2zbu*PXq+D9hbPD;^@PY22}hh}MUGL`1w z3X62o?B#msO6}j^&+oIawX8QDFi(8V>O!T+iKDP;%vkmO zl6$St#v;g5P@X#!E5S({4hL0nG#7-AbR^MNKIA#q|786KG!&GtzB&X`BvBksntZDB>j z){(;T>DHe$|90}X(Qo}5^tnWhMwLC(z$ypT2qOp#QqzsBk^(n%J$+8STulz+2o90* ziZFyJ1%-rw2!^a840vE_n&J6O2B#bYDo|30NO+MxU3zdum4QUGM}31Xw@47gv$ajD zB(wJ}I<*T7-_@@6{#rRsOA~-n6E9gw8brhq6V?5a6wG)%@p&kSx ziWPSzi(vm;b=Jb9+aBR%XI&gxTZgDHh@nmUu{VWY3oxY_zFYFxUUmyPE|4uZ2%cJIRiYYZVQo~S$ z8-DZkm`bhG+8SuH@mMfN1Vs{yyZf(|**iY?_xQ*B4m{P=fAAJD+yZ4~6l}#=+?OtafsKujx~r_! zq)ScR%@81WM=Cj3pr;1{5x7KtFNyr@xB&rAOeRP?oaufvzYtftGCU&C|0K5S`3z)p z3%|2Fyq~T5HtF?Y_faOdTH41?jh{14zMWWoRG`@q@cc2rlHu^4r4v7QN#3s`>3l08 zPw3J&;~C3Vd)AN3uQc3Eg_BdTxiL|NDWkjjioUO&O}1C-=|!F0KL3xq zCe_S;pof<)Et4+JrlPt8tgDx=p{h);Z)<~8w4_qWxAy@VFX+A;SgDJ@Nt! zqiqv;R9(c8M|JW`xl|hI)zvHgPjaZ#tIF=SZD&!bB6G-(*xFxbA`qG{%Rc3QIZk>} zrTkstpePN!S>*USfC<|Ko^7Ha#2&0OqiCA>wjJhe@45|$Sij;%+ezwC3KnAI>oXY~ zdE*E*cqw*OAKBXnf6#kWtP)Mh0Qw!nSO0-wG{1aV*?IEfEFM0-KN1M8XzFU> zt!hu<0dsW^axrihv9hP0yFWPHR>+P@y(cd)HQhLquZ__mO|CS7EA!d4&?N422b9a* zz1zC#HQ@5Yx4GKvNMttoPu9-HL)+joI4yh7CwH3uPYwiDU*cS?eYO5$l{m~@0}$sd z>;V)}a+GIkzb)yW3ywuPk$8kNVdsur4=~)VI?@8bjxOoS2li>$#=8{t%3=HxW^3XH>b!YUOOnLakYe=R|-96kL zP@WVKhMplHgK063v0(}`kZZUHTx^U&rGNzlX1nJKjg8E8h|@E(%mUT<7G`KOV%@oO zgR^({vFhl|mB&X-A9MOGkqytv;VOfXz%I|@z6{$bPH}A8MxTlO&H9+yD##=WkLja- zC$YD%#5~C^h#8idHhIfiMMA{tyJcL+ulInd(oLITZ7{I_O`>=%8OC(N^T0*4l3UKs zQX~J4#FA*n(=>XXVGowp+nXb{4Ag%}oIh7Bx+`1#U{Fw}i#^@A6nR_l-m0iz zi$}6i>qSbWi8l9<#9`L0qcMwAP`Sw43C>y3aF;Omqwx$z{6vGRyZWUX zB4YhQjOLyw=08i@dA8XU}SVGd8ltnvAPp6Kbod2I0bxM zi15+y9N8sKP6;Yk<@OABe-tjRWu1)zKSi-xVhF>#TlCQ(RHQQ}agA$7?@n zWh||-wIs01z?@NIFp>$kWu@3=rL)F{-%{4an=xU67o6B&@Tbg{X)8yrUl`66pE1==2{PQJz3q+A1+d6Luuh18yL4WbC`n2}IXM@wbrPn$K9y{b{d zrAB;paHyhYD-?0?uSk*8NwNQSfBz3vl|#o+${;DJRFo%twz!|IgB&0btUAUI)9uEZ z$ih$uY`6~ubpXix{DM&k1ja9sjY_J1=spAQ-NFFBDFkYLJ&8<-cVLMDTJ&`K#fub> z$nJfS)qKC8J3;G+FJDvXj)0bO(w()lB)IpX$fU3IrhxZO)laaAbbJ9E{b8q-AcF-3 zE~@B{z&B=jVII#$g=DFSp{MK=?{=EZlE0B5RwZr*lJ*nJ)t=;=LP^k>bpX_B&MRYB$V=X|l`Q0I z)?3fq?R@>X#eSUew;K2#0-OyNU?^j_RFre(Vg5LdG=OWlt75p_)G%&z@dEYt2AaSU z?Er3WKhy-mZ8YXm|3H1yfIwRz9k<#fVq(H=X72vnasl@<#M5UJFE$Q=+j*Uy?rSKu z51;TQZ{`<(0+snoEoc%x`5cs-?yjoZ{@Q@cJ)FlA0d?A;XpRdPd3WBIUWm_?K7^`` zl;z9Abv)UZj%@1z5Sd2vk5$R+3c_v}nI2XZtyJk4M+?JLh%!`T*&8$^R;HhO?8%o| zXOuP&cER-LQ>4+2%kY=MWcs5^eAP`h^SMvEQmpP9ztbxT*5T?}dsMZ!WvzBDOX>z9 z06MmW^jpjGn0AF;!zUbSF}JwyXZG$f$_S!X=mGkHA0Yar;7#$NmcN zbdk;t1ITZLmiiO`q~=Anlng~vvx!+ww3I%GV-~;^pjyjD6S-wwpG~ydtMh{8l3eCg zqCu%#vNcQ0C4$t>#F!nP6<@gF zqgX3LQ36N*0({t}Y_;nQJ5`Gj?Pw{vmh#hbHc76{p(ay?Q&0`5Z+G1UQ~Z{`^IUi&e=bU+;QnI2lDwzakPU z`MAsd#Wy=orR&!YBghOcoOqBff1dpQBC5HV^Qw9rRH;;C?W|SpQe_7MYG6K zNy;fnRhk>Npz-O|N>MtQl*d;&4E**JMd*$O>h1G;owHR8OJ`>l)N}Y|B2vk>;z9g< zdYgWj4_xu5%S;g`4S?QD>p>vBzS;J1hueGr$BD_eXUQ+S?-;-18;pGKT}dfIcfaq+ z*AL4t*~9ILrp6J^E4d)58OBaIK`7fwV*?i`6sjuP_=!#XqRcWsC98I17Wf5UXDmqd zha+mP5cxEo(4(pBp^a2`@1O5VcsLgR^%T*u)d*>ShULT-fMF3 z&+e@Wi9TarPqy^9(Hj#CQg;o>(w9?VBqdUjXOf5yD{o}w2KejK%3S@AsFb`;5Py!cZ%4K1=e zLSm5ecYqj^X#+Tn#m!FGTq$DUta<=N|40B?yo)hGsF3=9dO*ZJ>7NGP{@;O*oCJs% zu1E((It-B`?3!qZ(v>s$0#;RC6J|JuLv*}S zrtlgc&VfudjiKgA$6xUdF4B4GOEASrkJLRhU!mqz)L<;Um*4&JX_UlFLS_Du3K15t zJE%x69M5~7hUsi+Nq;{|Zb@KPZ7`E{F2*u3ax2@A%_hsFG7p8g5L{P0P6rA?E$p?= z;$1WIysP$b(7V>6YqhJ#`-RtTS%XZ!-R(wHn`;=INf4loyVx!B?We}$jMWRSngLUM zN`NV!CQCivRtBAJm@7eCV>+ux!PWv$eMojqCT>=#sF@1PL$TRrPh@7Jid>c5R$)J$jRMF;j`;G=x5(!Qmp+;{E#UxhoaobVEk6*QH`Pq9! z+<>8XrqDPg8H+qQEqnvCS8z8ek&^|=D(YLl+^Bq;V;>?m1ni+`kh}K@R;fXRsKe+j z6*pUpfr{T)g8gn~-;*@qgY}S>UzvDI-N8@D?7URgvyUmTuEnmEB|qGj>*JfZ9+3L! zR1m%_;q#fSw>gG?uuosSN@G0mpzt*pr?B_wBDG11rAIgqTBj{)qAtubj!slZ& z800xwQDHq+Fwr+oVC{?E@C-YswlTY)B&$EWDL`?96D2@o=hHNtp;SqgBSSNj%W~Y> zA)uS({Fm_Df@@vPvXr@?LO#y;xqBEis6U==)ZiSa(>X6%aV(4VkL}LDlj+t)O*idFMuat zIfln|l^>?9v}2Y17U$Il8QfUn;#8F0J=k*6q%&!Dl61`~q0k}#jA~@= zR3_H&O7_3ssTCvL3rl?BM`s3!E)X-d7TGW?r_e)h1qQb&d~oRU+=ioJ%r$0(SI#+i zl%N;)bn-F9V^`BUZsB}c>e)TKsbblYgpv66c*D2P^AmhHT!0U5Kl7+ zS!d|YW)zW0o9jQwy74N9MIQw;<^|Zr#;ckFl6V&L{&y-l{^_{^W&^Rbqz7Fe1M9_$k@6(q=?M$mDutRwg@T4;JYfK3(CDbF6g|fkB??qG$1j>+c z^tUQR@Tx#gUO|lChvAq)VAUP^DEayoD2knj{z!lYhExVCQnVVr8OurHw&DW^zSqwu z7Yww?*kOYK&KF!`n6vp~lBT2V@bHgLWj<7~69ssdNOC59-M76*1|)bMM&SvlPtGN$ z*Y=P{Gw0G4!;a6Ce~ZH;b72J6aBgN36rdqQSUL(fH) z^FPm|d${pE;B#U|nqRQTiGO8RDc|$3nf+E8^17o@L+ty>$0)(ViN}*sGuAa!?4ZVFZr?Be!X10P%KqdCfB zZ&#i`VD`^NN_5{jvfmU=@Vdxun&2>zN%|`ILCx$=Va5VEQ^(T$okaSU3FBlGV33HC zw9AOQZU%{@wp-q)auj5E$qG<`s=qY2Cb`AycTMtmp*WIKY`+B0>AGLa_TQu!9>^O6 z1TmdwLZ-ma#;w)`GG4N;Kr&=xzLg1C+D4@ysn{;K?y3loO$b6z$>`^a)O?s?)&Ha5 zvOghPN`n5*&?j;O7!sIa-Sc%@RI}Eop0U;S4g`zz8j7`g)ltqRXEUcgS|1>$jZ>M0 z+p>SDGOJQj2uBZdCvnRqr7KcC$0rSsJhayjeX-`kpc{@myj78P z$hitvuO}|#tI_#mrFI+U0vX*^H9U$4H>z72(4n|!JgMp$&3V>ouNzx0 zG{GG!C$_{M7N!P&lXdbNFwNnQyElJOGZJuLxUkm^Jjj9E`dK#qBiJ_pZdgU=Jr@!F zvE zU&9+k0bYf|OpQ^7k&M{tQp}EVwuy)gLkvn=D5zpV*YM5iBJDL@L1t|iD`17bTp4@~ zl=vgokW2UC*QaT0DriS8j8pAymgSv;-E12i&p(vM^6^yWp!X9%k`0%PG4_tP$mV!v zHbmvU2u(>M*9DXhtanXaWmqal`lkw#{f~?}1ZHfgC(Tx)Mgoa8`ebK4sKz1)9^9v3 zJKv|mcg4YRND9&I$uM_}!=?xiIe3HRA6gdLJ*0J~(O?%T%lg)KeA(AtR2of+R24Wo zF34RzUePWv%MhG$@0{oLNqWf){`lG8a@ev9)v5M=$K0Wh_>7@2umLoc^Ih z{WEX$0JIljQq1`Luo=xZEr3}Z)nqn53TK5QOakNMy_~qX3AkZ1;1*d@s<<{|@8qYw zhtc9T_&hNC{1O0?65O%Y@j5LUTc4oL4v92LLF{lVIT%VltyH2L))%j?V-fJ5w(c1; zEaz23zpYAUTP4gigLauy+D(@11RPsG&zM#Cuqq_jEJki7Q`s7;LkR{kbeB*z`i8e$j71I&CX98(#r8Lt zwqq_DnO$rGjG)->a;1YXZ&vMn43?=QL0ATLY#{5WPBjv_Wc(9Sj-04!hNj7_-AuE; zI_FAhnAQgyI}Ymf?;xF?Ja8I?7D3QZ;g%g7e1` za=7CbgieDF_LF7m<)gY%EXT2;@2$n=a35BDduf*sz1%(%qzdt5pi;?UW-I=KsEi1?@&?)8h_H6Na0muE`@_B=GX;HW|%13~(Udc`m_iT(rq<;ffQ zcEG?aHZXI^wFVvM^#+zbQJ9HZ>ezGeMI%60JoBv?&A``d1u|8TwJL_X>kkXBH}M3G zGhLfW?-^a!xZ*T3c|od6lML0zpSN4OsE++fRrUe>ZGcIVzklo~{DZ@}M&g)f&)ZyG!%`8%rzW>Jc}%dO++zdbI(^VVg~#~>V5HzJsQxS@;&VKp`c znCnSNh?y0K0IbwcZrxCKbm@iR*Lyjc6h=@IMq9eduADzg0$@mp0(g~^I2(@a#Ht68 z9JVWt{uoW^-Hyd*#d{r*23_~?llp_^k&{_co&U2=Ec`dA6x3D0jXcUTW)wC$A&POt zq>-D}V%1qE({k%6;T3ZU%S=cW^G*xFjdGB2Gm4Q1n_eMCLz!xV$`E1KM;b@8;nq~DaQ9`cE-oxVNZ6uOAYxiDSE19tndh3-d+kk0=(OfMN*Kn^b52926 z_txnL2pv9u3ZLJB>ohU0E57zgH!b34%LvU~*@frP*jsicP!qLsZuZ7Du8JA~*hL<$ zYq=@7mgLLAVkmBKHkyu{d)%e8BJBO-dXc?uBeAOZo)6KcJH!+s-VpK~S3sq0r8sHU zdRBkU>z=+PO_qFj!4cs>NpkmN8J_YDe(3;Mj~awuis~%JzN6sYh&CDxJM8HlHCH!?D?4tHs3<2pI|6>j#=3HBYp-`#Kp^a8*BrS=no11Qn&-_$j zr!f2p;1C!6HNoPBpUgPBuLuR5qd?Liqr?n!c?7lp)pvg=9OXYevYZV{h(HsIn$Ha4 z+;!{|W@hXLh@~D6$?es^i_y^B#GXXeR5P$N;3C;uo%S9=5lIQm-uj>^q;iN5#^K=Q zc$o$fTp2V+xm07rB51Mp=g~H)NauPPc@H!OUW_E*o!hvl@fZ_0sLP#RrKxL#pbDkct!S_b_r zG}{KVQgVPWS<;!7#;QNKVd~Y5khxp+B9j%Q_Q^YW`UY6Y(k&CI$s1aKS&}~l?!U?! z|AK)U_7QEIpHFT7*~C-<`t9$lsu`jmUZF96Sn5khe8exK9@&qGt1^HiVj=UsaWSf8 z*VSS^#G_oxZusinDF|Y(UC3jlUm#*S-Iw_#l@=!L$CC}ZJdF-H2hLo9%&Zbjwr-OftlVzEdjd%FZ( zsPZmaZoKl?oi;MY6^7((o>l(pyplwhwloy&+(z0Qr8#HJYBJ2UlkTlr9lFvsx`Dd% zH0VmNxG!LqWv%j-wtpzArK6txi0(yi9nyOKsS2pq#M<)n;*Dm)J-3CXgc#?Q$j~>M z1`(P+sWt-0SQSfxxu7l?xT7eA;%FwYA`vfV5Xc_QWul&?ht)CW@#3HAC`gcNguXB; z1^Ck?U0kRsMM}?9APocdC(2WG*Dh@{nLn3tAZog1vKVv#y~f3KE*>nY1Q6bz1HkmX zcKLiQ4;KDyOKw%={?mi;AN_WYRt1NQCmAzR{5TP>29b59^688`WeF~-Rl%OTV07u zKCBi+qPPE=i=>;m8ak_0hP3P^Pb&qlpqCrSoUN|X(sfE$GzarnWy(1}T5pJeWLt7H zs#x`*pJZOSSMOaP{K)CT4W7H|#&G-5bd1=5IO#RPvUk|nSUKeFqw;*swxz4V?I-Hw z@2y#W{P?y)GlB^R!4?Wb_kd;vkraCY8v!5ZzZgdX=vOU8j0$5)!+#j;6zlNAyccmC zBu5T$<-(XUJriX!qIMGxM|p$}4N_Gv-a!zMa{0r4h9?(~@wT0Ul3;}ivJQq*(Y$C5 z*Vfk%-OVui)0i;+)4SLodE#N*FsiJ;$6de`v7x1XO z6iph5I(JJ8h728@bQe5!eSq-q>e^acaKC>it}4foXecb+A_b(B>X*j0vAR>4 zYFi$eMcoTLE;Q*r zuYnz0)7RYtzp<|c@UtSkGBfV{6g=S%BInF`5l-`=+a`>Bc4s5%Fg_C{V(W3j3)qrb zHP#*{qS!*RR!3>g2!PQod{rZiO_-Hk-aeW2*~EDY<$GQvkn%6?3Y9)9qYNUR>XNWe;tE0KP;tZHj++nZM6gzZ`V91I@c5IiC)i zf6}_m{@Akmx&`YG!H0H*g6OSP0}@q>ji5Z;3YvA5QOYujV*3NyWTP1#p6+5-Ak1Vx z8&4zktKqXR>s`WF!kG4vE+5Q8@qS9-NF!?nz>=+Xcy-ooy*g}`hx(9Y;})xt_mH9LgEhvdi{`cDCs7l+Hq~#p@y7#hrZ3_ri6U#) z3nL7U50x*O>ygdATnOj`$y%FOFvvVtIgAm6T2rlbPvhuWgx#2RSaH`S9RQ`rUT(mazCs|^$BE!Uz!x(6~`!5ldL)mt8-JVY|-}M?0u^D?n)!+&c=ZA%f z{$gI4a(n8L2w~E_9zByA{*tFJuX&>+%(6FJt`)D#>dXObcGPYnjncnnzf$OZWny#D zWq!wWojC{&)U-yi2bSDK4N{TAbZok~VNE-iO1%MaLv7g>h8mOD=y_&!P$)M6x8San=&eIVLNFITdPl_ zQ9=g->8cnxP&9&bZ6kxAUzwF-kF9iwHM?B9%d8W>yksETsWg&NYFRpEL|6@S&bc!@ z$DcPDXuJ+qsKpsU$ad950I|A3qdN84{?m*{%QT|&*M!RH0Z4R3f_{=1EJLh}v0};) z&_MP|tk~qStN*44^Q(o1juqF}jXisHubTj%|H8krTyiz4oGOdE9$ta*Z`&+SIJ;}9 z;T&jLb0t~Ej6RVQ^)wTF&Kuo1f@Jiw$lrN@PZ z1xB=1_{PWkIc=UXlw2V!x#yrhrH2gi>M9zENrtGFS&Ck%K#|0fKmVi~oKHo)xLK^n z4;FuB-`7y7$3P@u7sHNHh2K%vA3PxZc)SwgoBMgyA7c!&Hl)2IA9GXkIBr51plep@ zLN?SrEtjflQj87HHC~A}M6RyHlRGJz%J>GIq=g~yA}}3z9$_mP6uu0@$;|F=U@xr) z+NDU$j+Q6Mu85@JjjfC`jXh&nX^B_DPnN(K451Twm4F{L|~|ACk?`!~%1e$&0%bUej^Je6A~AbxDF1 zSFs0{PhcOj{G$}kx$;t=Yn)Oqy7$5B%njKl(?oTMH3q)=I#bANVP`uHjf-(b-yM$h ziH^O+noCSi2J<>4%a9TbI1V?7EwF<7Gr7_Fvp2}jmYWvaj6X|Zi*ck%=UitC8v5@o zN90CT{0>}I60LT&<%sx^=C9M?7#k(ebrLKpP&ASiY3@&MU4NmA760tMUh984E3|9- zP!d@%cPWE6z_|{P!gh=8?n?f_a)cUx6{XTKe636Q)F^sK=bUichG;FJTGjEzbftNBkY=2e98p6Fz z7TM`w*lB`!vRNXY?4Vogl7t<#jUj?Qiqis8HBoXy(McZaFxs=KAE`U3Xm2P_Rx*~X zwAEv8rW~7DYOcl@k0fdefFn!>%@ zry&-=V{#L(`L8P+E^4te=U0<<^|j@mJ2;dY)oyA!CsMO77FyrSt|m$k?y4k2zQr*Z zkN1P-VTmeKk+4!m_PQFfD6u+lJ>iL19YBP=JgH_J@9?jA(f|3Co7#Wl!D-Hfr7huK z!ZNhk>L4vBhGlo5WrZ6$NnvZm#v73YWLQDDcCm|2Bgg0 ziDVHonk%tt-cdAXUTw7-z4PXx2|bLjPH}U+pluK$u4R4y6@z1Z{LoFUmt1{VINn5e^)jzUM}zFEpEn?F`)IuUsr_61 zWlk7!V-UDQe=k_YsF~JMkRgot`c3?^7g8A<=2kBcxX#P%E6E`xS}e(^+^EHSr|KlDf1Mh6;Lim@i1( zs&WS?);`X2v(BxwaH~8M8^g?7Lg}~KYJ~%?4##sA_`y{73Z2J#g!4V8dZ&whFUh$S z;-%uN@(6yJrj=F)Cr|TTW$tQ~$gRn#>&lOEl$wUN+C>XyT9k>^6Z~umm1StjWfMf* zQbsyJ8>F^peD7BR2cZ7P&MR7NMZ5TVR(M}S1ld7ZhRk>cAPL)jX=FtEfu}e>V4YE% znXRs-ZMbKbq~0XvK;vN2`S!f8HtN%g+q-)wd-d-IehB?H8wEvG%~{pK;>geJ%;=PD zf`fEAUvv+OSO!H^L1-#anUQ#yPsg{Yn*B`PIukhP&Y_u%yHL!C8$AT2)`Ic(XSYe&N()}b5+S+6q^8_+@iYsNuo3E8D-0ZV3J~a|M)3SN=*;y z-NURP=N=RX3v9j0w3RF(__K-?kf5)Ay71+;2lM8OK=tQmOWNSR4?c2Ia`7fB{ec06 zXg2ET37%shi;p!#=>vHTmfLn*0g`}Pw;}t9)t2KB>!we{O0(JOdczIkP2|@VLAt!O zJJhharB(AZw4aHI_x-~K23fSe{S%VTtm2s;60u!AdmXRRF-|>fYf8j1D4xz{x6;Kz zdPu_FLdHc(Krq$RRn$2Z@B_717zq{SvW!3KozAy-tN1eO+SV4zo7?l+{;qu)|6kEgGLNcrCI|N+lMnsD5?aFui`52keZuEae1ft5eY5Sie-! z0Q;JJqetdN)9}%zyYCoBR%8aLqg}<7YJFxEB2IH7}X+7}?KXaq@U9Wj`8w$+BWXhy*n(U^M91 zhb;2PvmPJjE*ls}Pg$RrGc*u=V&*V1t z0p}1GMTKcD+hySAO~z8fy5NCH7Nlz$0p?@FKP$xDh^y`uvOKX1e=dIR?xA z7i(|*)^r@U4NrQA2!m0Az!)(~T2e>4(XfEgqd`EVBm`;3M1;|uBSuI|8AzijU4jw= z0Ra`e@SS%&$9){n`_t|7H+-+-dtPTj>y|dVDvPqHe-9ff%kv0Z@k z2OCQTi3Zh1_{M+VmdAHROeLsbkXaBbQC+TFC5cS}2WF3L;A={FxYl5Nb+@I@A=>)wm<@7QO}pWw(q{ zTxDD7*H#+ad-TrDbj)F|9(FEyHXL>_|I3~Q#_;0|;|Hg1 ztAoMg+zl7snHL1*(r^?%%*yToOc-z;S?V=g?aFUWA|AVsTi>5?zpQQo@-sA>UmjLKSt6?S$sK+Z9e`ZPB@4H8Ff5UHh$>f5^+dFROJiLH1#m{1ddbh zOfP@ywF7n#Repf4JKZwi>n^oyEGC=807sb3;yx_jnBr)C90U}+@6o;CuYQLaz}Dk! znbX_<$lDK;QnEl&#Upnh~SL_MZ9PV!U0j zTWapvIfBL+GS)-x+5r$P<=A=`ip%i+2;83?K!tk=vC?e`6p+AFn)5TDLtC z5MWmW}xK0mjQF?6+EcqvVxS_avAD)v#w4#4kru9VA+c6lU#Gn3pBR(o{^Xy zxRhvp9o4<(I?SVNgs%7-#gb)|4Tr79Inkn6i5SJL(iFyct~`TPn~B^<4-IHpST9-@ zHr$-s!$I{`j(axtabC;7Vfe!Sx;gcdR$MT>c{`dy31b_vR0u=o{ZN zcV_rz*1j`~U`Gquo~qN8=9Ndj<^OaZL@a4VCz|ZtpAEbdVPq4hj}OCnI4fS9N~qXQ zF-QeNPA$f0#~qJ5$OlMe#J%nOgfU?|2B#;OYs}A?>i_EAG!FqI*VCMn$cr{^sZvu3 zrr)K=L<^9Qd!}HrIyVj}-BT5%L-$<_rH73mXX1Rvw{xA2S*Y3WcH!F?B|3Ojp>h1Y zM8352XRK*5tkWnbmulc{jsa6p#FVF3v z`Eede{}*0PazoX9{%^blE028oFT4!m^1?v>fALbLt3mFi;|NLUTulZ$rU6rHSppdl zX)yRdcxm!d3DZB9Z<-iF!^=uuBzZh0dA)JwhKr0{rGXIZzVs*W3K`(}s|&Rj7rSQd z9tE7^s!*nNJKV=n!23tqh2LoeRPrm0fD$D&&Z}f@#69|N0@~AXF~)DIfE(6xYWvUk z^D)=F;>N}X@pA?`RXZ=Hvn(Ku%Qx{a&r(+Y79`bU)n{Mdid`k=z#9;=&YW+72!@1r zz~Kp8@B6PJ(NnSrXV=kcmnn|OzsT7)41uIR#?AAy^$`9dXGi$>ZL>5?K71I?HA#rJ ztP;CBC%dzhqEF3a5G)YILrH}jDnmqqisG7&*{_R4m**R{rpmK&gFJc2@MRu{)iWwX z95m!K$?_Q?@nS5~H;FQ?<4DQe7fqcs&lz2vUH{c3~edIxWPO2!{KkSZS*h@ z3+Nep%a}l_Z)dX;GhNGsMXu@CkX-IuA05~7zD$PvYk^r&g;Qr6vmb}>LvtYXEFJH$ z0ButauKcbkPT*0AWeCnQ^Jw#^VXu`v=EQ|JPYl8fD<;m9nxX zPn4we1d)wZM>kqr21w%cOdr^V3=tWox_WPF^ol4kA_9@O*=3R*$Ly_iHsrTDKT21v zu3{d~DABkfT@gI9UTk0Sgx!$)3-e-vWf70tN{CI1vkI5|ScIy~0!+V`=NG4MqjOuu zE}!7Y91P_)!p{3s+$L2>%Q%ERLU5S^dO#PfGol*yaY%{gVamgIN&D}1%X1IO}qsVJqpL1;k5UVmKbm~A6(PJ z`V~)hS2z#;o^%eJbi!urkZ>n|V#<_%QMx?M(_n!^$;+h`X zt>AM4YKC*0bcuGt6z%EJw>JU5R&va4erJwtXe>=^Yz08UP&3UVI^L!k0b35C+f$Mp zT`Np|ES{s!D;5Pe*$w2oEjx3wduQXRM}6-{WZw1DM3Q^|0<3%(QL9RWd425Vpl##N zmW$1_aWh1d_rathl(&2}l9`rhXf&0cBfnj2t{Nt&Ifx(cWWX+FQXN7w=q3y*ZvHUM zCV(*sZyX?UVIej%!KeK-lcaMF8xk5pJ|%shM|E30?+B^Z+^|tjqIi#2f-wpkb$ZNg zzNDda8biI+CSBKn1K~a7+~gY?*4yJ=kO;X+(7U_P>T3_h`F?cBj5Obhyi`(Musu7O ze&TVa@Qch+<$Uwep+GpurVTgS^}yl2R~)69GmiV$@~3smBRYb4BEj2c;eqgKr>Je(|aNG#uoj43y6=fVXGR z-@bWKalru1W0W++@mrMcrCa5+2QPs6hq#@mWG?1e04GUb6m)N>k|(WIszNl4{&y@v zrg^;*eD~6GRoB!QMie&FXRqiM)8*9V5C?oaCEF#*-y##a@=m}TINBsBmR45 zJL=gr{|D)f2PVZ|pO~u8df9qMX_-z?Tc0S=XVHrUjg#sT+3LxH8W-Hc(w7;B`wiNsgmWEPxx&JD zd#A}}lIeW`z%6kkN!CYF0y@MWsXA8NBYckFZIhp{*d>E zco`#&wNDN6wKdTQ0lKwASIaGJV+dphml92K`7Lg#x!au};(K0GQNS*>zMY-B#guH| zm(i5Z;Birdz@pU}VdMrXWxQYE)0&fPem5Zp@m%wQQO%Hy`kh3ogVOW-s;p>nI$P@+ zGIrZ#ghd0IMl%$$@-Bv^8e~Q1)QbVo(&Po}tNdNLxzs?QC&oDvSyNzTp1M%z+xEQ% zzMmmhSM0o?Q3F4>?o*Sk^<%uIDi9=4Q=t^eh=o+#1Y@PvR4JZCKDw-R<=1zh)_E3V z%*$LMrrRzMx8DJe_iUpECUTO!l{}aBg%i`!RF zhw;?E(wpr2#|*rFf&cP<0}H8G>jlv`sno4}zi-|Gy?GNN^48TXsb)|W8%S}_1}*oK z&b_Bycd*pzh`7Ojr=X=6ZfFs(BB;cO{4c|D_l-sKWo`}i@Mm_`)x*TY!mxvBUH5r_ zdWL2vjgz91*?&#XH#i%lw(#5x;b~H=9!V2Y{zeeg$fOk>d-)@d%^qClllxA^D8DI_ z26mZ3{Oqh}YO<8T-w2u2;_Q6lNE$f>EqD086kLf6qmk3VKVPKtJKsU)!h|f+Lq0sd z@~}dyKu%-))9XA#`>)adKm9gW?n(YB*wH~+zZt2$0MUcayfZpjQ|`sx#^xujC$f#^r%CB;sD!9mnc7$Mf}=OQKHK%IeAF4;s%-BQ0}pGT)`D!sVU|5U4Hr^sVhO&q zpeFljC7H+0Z=`3tIK&vdlkZjtWU`H!+7O0l8yp4}LQL^3U-wFi9zy-~We6+UD0s za(x->$7gkJd#NB11f z`!dY-ChN9)&kCR-TR0qo0+zZf3d66PiAn%hTn3Y;X#_U<S0QP&Hv%U89E&e#vo z$O00Q2L*~=)LZR{k-K;dmut#uq4}p@kIERY-(Sczvk%xz1hU?cGc>*`YFBvqeuG0K z$!@yDnc2xPs%z&}*6TlA=0oGb`rEZ)JurnG9*Do)VkEuu$%`%k@!{Pv=*zAjzhOzQ z&3|?-By)8jBzE&GBG7lBHVJeOY@d>x<$Nlf)Q(7#+0`_& z;_l@qRpJ`KXA~JQ&b{8_PVRaz%jCGj$_3nTand$EC6r$R6s)Q4S>R#Bzf|bc1Dm=V zI02L(eP2uUy5m(-?^zU0WRY};w=>+mjgJOnoo+^uCGBof_G<6mC5eGCT4tEpy0G+N zurs`gvTvJu6wt=((xw!;(Ad|HOqaWsNe}0!VJDB&KWg4sKdZ+$kR$G2|6$NpSL&)8 z$``P}Qy9gDIxL!(Eg!Kz9OpiQ&<}GTSw5OjL7Uz#zZwv;Ea8)lGK6iRW9U~};H2WX z|M4qS_+K9@pfL+hn5u6_@{InK%hsBa;eLzqeoN;eVpq(q#GvZuVAdUhwPFVFTL&TK z@0S#>%HJ?k{(%rxa-a9!EY`$KS0>!%{*B0Hk+N(~Zx%o=DAg}Jj%EwVUd*aNQ&1@? zn!9xki+u#pZ*|)*elxDK>_(wKd642pIdz55U4j+J$Vo__y_dAE=!>iqr^$1Bt?^^f z)UcoFR#nDG>!Rnl7Q6kqGQmeD+}xkuLT42omF_4h=(^v3{lFyp#}_kb@U7SB5Bj@~ z*LqPOvA#b)Yaxd+IS2FwYdKm0+&+IK>7uj`%Gd91EX%J6M6JrV*Mq-(`rU^xFcnMS z-urN!O8?vP3^YUg%XloKl3r2p-2>M%afiAr&e!O4^vt0g#zDeR6q3m%2=FVG;Fn2G zUUUMebl(mrB8TN(;~^zDf&!3A^FDuCB{B$QpLQ{KPe&$I5{=Cki!!-u5Gz`V1FAzD z*W;uhPH0wKg|jCZ^~_lTv_sCa+=SZ1VsbU0xwriHKrZ0OWhlnN2Lh!vX2~Fni&Ru% zEk#VVtc4iB<@TsPfmeub55i#Eq1m*2?U-6-QDpnRnK{vv$Fy~qxzYeWC=t0a28-pM z2!}cLbuB8k!zPA{j29{M(w-VgYkgl+maF~f-?{o)O0V@IoWhUMohW3y6mLo=RAbk8 z?174pwU({&8%G7vD3atg`JV7&Clz8AG`*HrJvG3!#QlThN=_2!Suu2c$EPsznv=A> z)4Q2>X^)nwdoKDxHAFAoJM`(TEjK`!E;49$U)!8{@bu3|a)bsaq_OPH9Oa|-Rg0q4 z#gXSTyNcfncQz!QE;A~9-zups1ZQ-A5e!lM-2#m3R6GVIlq!I7odE}rpK32~ck?F; z#278HfA|JM#vLOd5a`88o)pLFQ|mdB50V(&KsWb1H{=UjMsVrKn`*Yv!NapfJ|7xc z9K~`%W{{Y0AQcQLyV+)wt-4UlbD`g~ohR3>?K3{(Lw=Z@b{OqXmjyf~?>MRbkg`=A z_5)J!A-NGdW@f$JJEi`tBRs`HSSg7T>QZV=R#fwHZJF^Y*7Jl!2sWo6Wxt2lHqYEs zyz0)#_ucZ474D)s76@y$^W_rCY+@0JdFk%G7+a)`w(@jNyxH3|qDa(bphvb-3^`B+ zGJwt3vo;0ZZfqGnSt>(!u%14|-F6q}uvuVFl#7}V>f>^s=nwVpR;&MWS!v&{j*VSchTbv%mwm#FpHd~{C2%4!tDQ%RLT;>2i zF!b`!b9oeVXxA%K|12?he@^sDjzdYYZO#-fFz|VkrArco?q|ZAfojv)Bi1i(eFo}i zx#p6W?dx|fT|cIuF&tf2=!`A;amR%3uQbxO0M37_Am5rke4-%TLRP>JcW5np&-TQ&c*qp zYDYq%U#c$4V>!3+FSfUl{dGbB;}4rcsEf%`Q|J$4SOLr>NX1YFf;MO7I;{j2c~jzW zb(-E@&79r9`zP5Z*nO7AXzG-{g*GOF|Ahn1F=HTc?vrbrteI-Cio@LZ#qc2{ABjSl z=6XqpfA2VVaZmu#3m?3eYC!srVe11(=a(m7J>C?g)WCRb!#()0+2@pcv#TIR()H#c zb>F~F6{||4j&Ai6lw_4;7j=*8?UyFRG1kMOV30l8Qu?05srR#vKToyzQpAPqWdR5< zvnea8i?OMlZop$+5Eh6V71Uxp4ZnYF(#j@oi)f#LqOXwQurA~8JET4O#5D6)Bl)i& z$-l_S92yZ3yW3Zq9mIs*cQoI2B*q6#Ti{_oNW)=6@??T1DJALNwKt!jZgA)y zENwa3N-zV6(VsDuPv7o&o@ZqmWy-OXm=RBu6Nda}GMR3WFCCb1m{@_tdt{0{p& zw24acvm^`TmJ66TempXD-;<8j@uYuue08e{AEdX_TyN6Ob55-@Of$Lv>lJI&r4Apl zKA9rV=Ux~3L=9LCtfS`x}JGf zN6l3mBKLAeiT(?-z`G&q%gtB0oPJ08yc^FG`%{3+V#$2t=T~s$(W22(`P^)iC=?fhx zA*+UjEXmWWucn+^?U?k&E_!LSMhevna``Ea{qH7!~TQzcK%+qz}_ytEo(eRr}_zr z*d9-`g>q+#jh;17zc=TeVY%GuM$Bs{XZAO-&z7dz`_hC^VN-%-HGx}~WS9X!kPb86Z+pV#ZBv5oG^<2phR^CC_1m1` zW{UZMOr?S9hVK<68$NNM#M{{vyJg2>og%p!k_Z@G=RV*Vzb~{J^}L3y$KDcM3^h6t zYzx-_5my$lLVB$D%1r8hJZ>F25I^w>v^RICx-C|urb`RLA^u!uaJor) zO^81$Yteu1Cv*~c>#W79Q-u2U4#}g360%&FD;(E>f`Njdbz0Ynn@6G7R@5bm-D##Y z*zc4=iUzYprt>*EP@V7%)KU^Jpb@G!A#TklIw2}q(+x4NVEAgHI9)#GaK~D~EBEH} z3@#wiRu=!_uC+U}4FSSBG=VjGJiuaOc4<5>ZG>CDSfNZYiy>F*c!`?_iJ;@ncOCJt zAwdH^r^<8(c6t_}SXHVN2N>F);X>tS$$8YrTapejPlA^UUdfFwRuCq>ExV*$Vus%( z7_M+vhzSsP$$R5HQdRL9to79dD+<*q|2$>4w3vMxR+mXEtieVNR0uR+3+NQQC?%|) zU=4w0og>^i60S%YTTv21#wKEzcqX+tk`3qio8eIY1y8%Q#*6;>Ipu(X961d|IS_(8 z$oiN8n~Q)~wlpdP%IDDXm~0)jOLo})kIg?!S%dpSUkC!U+xoiwuRoGfjFNtlF)3Q-n=-)G6qCrB#5KKO za32j+sQa7a^tw@xd1}UcDIqvxE!a3uUN{)WfaQ0YnqFv*eQ=_Hd%rm4dX?*)u|#`$ zdv8YBC6K!Nnb&cRg}*rGG3|Bw>T)@MBXnh#)~3|7N(y(4<6pFu+qu5j>s#&|U%L%- zLFBa3m+;4Ny}8>UF?hRg0%^N`!4paPRItF-`^m?fpDQF;94Lr>_X__f?&I$g>Z?ch zO)}{ukzDY%FE8SoOQT7ibDOV)DbXTIzuU&>)*~J^xz4jw{B#q+#~SVBH#Jfj9|L-& z#@3@3w7Z?H0#21@CmBLTO`xZfrJka~&nOy3YW-Sglg)c9N|d?a1R70KPg3Foz_|6< zpo->KOjYs}l!fd@yz`#uyn0Mvl>WrB2@Mai`Y^_gs2+WMmY|YpAXpyy-6Q|gXKyxD zq{6H~p)aH--_(R8W&*OMs(F=!;!+KA-TYHoh&%?ymQ^ITXi|!5q?7k4l&*cB z#f zuAFAdN*vO%WTGHg03!myY{hVoP!{;uwL3S{#^{D~ESvyCNj4K>{cJPOR2Ka{PO86Y z{P_G|EZlSRzYvB&ar&xDql)AfeWBGLER zGGsKnxXJ5El;^ihskzCb3cY1ZuAy=PkL56a8BbYnOTQ>v9<=hP+3bBP>#_o@<*l6w z_TIjkJB;#JM>nNVsQ>8&z_~!KEUn&=X1HqS(y#hZq~QOcZ$yJ)J1iOBP*L;sB8@VB*^j1Cm%waBB}xe zqPhfN*2!;2ntxv$&{<8r_~-OJIfeJ{6WuFnT>9X5 zk|T+>yZiZ~aEST0D+p9qzRD1H*s{yuxfa9#(>X3|_y#5#m}sW<5)LuEE@Gry8RW)4 zh!VB#uoCr^dq|kDS2T+=NF>ZD33_N8b@N)lJ(O3y^duDRa55zN7lF-k1&LP=M6XxD z)zCXcr%s3ORVjV$w6MEp1klsc11w=F+MrusU1&NC6;F>G=J$fYF|l*EE$aols9S4A>=vUjBl-Fu0gNRJ#JCv_cTY&EGASx-9+gPS$W zMR)@#6joy?Toi|w0J_nYQMF#qL^1zrQSL*p1o3)0j-h+?Op3BCKXi<|?W9tNWi9(S z5cbVo3>8eBryZW6697_eSSAWvHB)yOqprNJvrrYb_pNl5d~dKwyK{d?YZ9BI$p1(j zYySf^7H}q*=1gp)J4t1c6?*E$b8^n2sx3d#XHN3M{0pqBu>vxl2a@+FjQ0YJjdT69TYy)M9d8q|Fi!B>V0zY$JWRF6 zN7Bpc_eqi#ew8O@ZqDu@%?j5~3>w21wFir+PUrz4F?&~!da8l z9x6#k^E=eu3ZyQcbA>L0nj^+ebwgJ)=6L$(883tXE|wBFT&@(@+*dSSGSD9+mF>z{ z1E{-WOolI8rO(0I0@|)Hg zQC>3+Py&@{ZFGU)CWbZ5(Z;?NP1HRR#{UjVjY-)5!_@Fz{PpdF>)>F>?$l@F zua;}Qp+$4L?rplz%A(7@(hQy>Y`u=~+0W>)M7DxZDv6>zktmvUCKt5UUm%r9KEusU zXTsd^mKI@8Bh!av#P?Qv&yvO|feCZJ{b**j>E)I51;pbE03t|601}0$gKLL zvs=|#3MDP0j>4xuPTVN(70;h`bs}Nk>)IprE+FTiAtd!{{HGa&?D#bCHIB@3V|K4d zp(duUv4vB)T(bv-zt|^_YJ33?%^saErM_vNXeijqeg4vgl{Jb07d>yNb-max0`%v@ zt5u54K-;i=pam& zGZU{>xa4Ae0mD~)oA)7jF>?eMzG#D&8y5jc(Mgb#7>5Z^lEy2>Tt)Lz_3i8nYTncw zb?JIqDLeAJxC>qnhQXjPVB=zIkG=U^$CGY}OpF~SOFRg_%>%K~&GFC-OZ70SBwR{` z@W*7jp{p~jan9zjYCw*-+42nxtIBr1Ijj9CLmom^z_vJl`Hvhh;|GNDif@t18E#6X zW<75yKmCX5G`RK$R#_Z?dmi0tDrz#)YC6R{(>`S+bNHV*b)J7$7C5@y|NV|nNNn*Hb z#UWi{kEB%t8H_bk^m!C#`P5<2Y z5SS@`{TTP@=v57He`hUH@%my*(jW6<^O@t9)L-Y(A!#g`uf`hbuYB5~Ja+RWtp7p! zo89y+$1_3(-AW^>zf8R*t9k?Zs}BJ`aK zK{(L^X_FyNNHbdU3i!>tF5e)&vE&sZ{*}f0xwsXU7==S{B$u_I3e9L;&@#AiW#V(} ztZBQLjYRHe5>CHxdlSyrOQzld%zVzW8BynlI-@s@UXyGicinA<4RDZj3^t+~j&xlX zO}E#BiRa^wRV4s?MyeQeYNB2a+D@~6C+~FE@}{n(Mw>1v{n@8M`}p+49eHiTgtqRNy{bFZyC5eD4&;TewO6{YLo5IOu=P5v}haXY>zga_+ugeBY~X>!UWT zE0wof(0oDEg`D^$MlJ~xLePpz!4$W8U|;;vk8ve@{RL;2CkC2XqBX)AG$e}yigpL; zwI8uEv*RD+84o6ZZ)-ViUhggU0qSjhb%9nJ1dH2BH#(BdI8P8HYNPl=Ym>G!qr7hy zp6CruGOmNz_*(FS2}uQuBF+!JPMj`rh01L`MGkR3lr`Vov@RE}9Vt>!t(z;^i|5gK z*>Uica=qOMkvNtQb$R&duYr61y#bY%8p%Ar8SnjE#ofGjM0>w~~$G>v$#7sRr{XJ6DbK0DzN+ zsQM_2lxH0NCL^lwb8)L@ks`#Mgdl{AX7CbNBxY_APsPqyQvHWP#)nCYSteBPyzGj! z645QV>IZIEHGP3no`oUaZVyhTVvKdnqW58l|7aN3)zIRbjY0pQXmI)e;`7-O?aGoA z=CQ+#@eDDt_x^$#%qG*D>VhcCZqhHC%a*>xo~xNPs@J2Ec-lUA=<1(K1x;7;&yyw- z*js+DPK4g2Z|ouwg|5DRt5=Vjg&3n}%CXIXxA9tpkVeO#B)&Gha_9x;!pkePdU4bH zg(oXro<$}t9HM$ILzmKJ7syYb$Zu@}#Ss*jI6L*LPixFNUO1*PCy$u2oacMqlp~)V z-;>J&>&Tj;;NFKF`E}o-KfH5Jxqp4cL&gskb((Xh_i7<#%jF!WWPUqtw3MpSwZ~qp z|0iW}CnBidlhgIh&zi{}^?r;;%bkp+_nwp(=HZ!Bg^h$e2`@h|D#vd}qE7X;{mBJxt0h3c^+lYn_>hNZCvG>568 z6N`S2O!CO>^ZV|e?R3751ASFv9Z-zb4c$3^?XWGy(tab=E(5s?!j`MC8K;+r!#V)y zT=nf(aqOuUsoZq{&F3~K=^jqif_)Vl{BJ5}UKi0+GV zuyR&f{`jO}ncq&+3Hs8u(^3^`1%nXd+jCzP&}K#E`U6vvkINadhr@iaDyg!AUBo$`R_ zuOtqLOu@l}2h8;CinMl=OI^U{(4$|$A)vC6-4)e4bDwOh^Up+HLRTZa#L5pBjvT6O zIbr>`C^3C~A0ND~${La10< zbXGTc880F5y;Tw z#UT3n&@s`@#!%GFCmvbOzK|N8yhOwRDwpVim&!;{NT#cF2;%>UN~C*`YvUwO2dGL> zVr3A$qr299nyBT}J$3ggW4#O3z}I6dRYPKYOV7MDhisPmjL#zzO;=u(Lw{^A8>KwY z&3aQ$50+gt5=$>hqAmrYt;0z3Vy8hDrJut>k99kl5Eht-!Ocsu@- zTlye=ARau(bWkl6`IWUG`x}ig*$00*gJ3yWyyw_Q!Q#f_1@z16wRlub8}5Pvh)Gc+W6cd}bGVcK;nT1aI;sm#VI_JX)|Hz?ZZ}JbfCd_c*#Yjb88WkXe{g~j{0{v zSpQ=0~kImS*GT${nc=E#-9wNNQ(BuKW>Xk1omRB1innYQv< z&-``|S=$4=M#+y|YI0CDcWWbmrd_`3PojG5bKxf`RFT6$w_W`)1O!L2x;-BW9q@kF&5>7{o9YP= zM7U>)L1=FyxTzLQMOp%VM6h;N1yw{_&pk^i3&-nE&U&|fB_~`y#anD{pQ+(w%CTdydGZJr3~XOB7n zQ?al95YgXzGWZ>xv9(6{sUmpl44ytA2Q~o?DcIE9*Th}VHc$C)ijGM|(Vc}|b zDSY-$icGQJm!Os7zE7D9%4i@$#Qtf&7q>LhWdoeR9uvB#pOQA?><0ks;-W4`e92V= zdWO&W+{u)xhygn8IwxPleO|ad3$tBJYtX5NBm=Oa;IyMu#8w)YW{6mX+|ig@+NBeO zO~x5j3>LNZ+lHE1IZ#U_NkKv;&YJ3crqAqi-*&~;x?nsB*kneWjZytLRG;n_2l148 zi4q}u6;M@dvD(5Xfv$yDx!6v0?uk{@CNgpY+1H^QK&!p#Y{1Wh?9~f7tktW=BvylZ zGW|i8PtebB=Knbr=NbQHDj3zXryJbwakIzy+4TU)!^~&hq3)sy)aIgo-_k-{Fjr66 zhuFDBf>bsgKkd1x{_zl-R@c7B@jztD&Wi@yi)JN+y0>RD8KYDm;GXfJoY2g40~0*n zEX|H`4*)V5)isyARDFCSN#({7bFYI(~`6uOf%Tx-dF&-3p4I6tLy%H;%HH5_$-#hBqD*5^xr*M?AV0QJjVS&%@ zntM-Q#;`@+BArwia=#8;-WYnST1|&E|_3|%BHaK_moj7qB5h8H}3|CvMH&n&D2b~&ZcWv64N-R8mO;Ci;Ov~AbE^J z>Y;YeY$vnutrT(QFbM71vI{$R0Temw82au=PiE?r7?OlOVUYdi^^$OnQz*>xzPxJx z9pN(ZvF=uA(48!YlhnqyFNX^beJA{~F2qh$W%Xy}K~<-o2fWraW`#@8s>{c(o|g>m zD6VMVkWBq(9$viY!1?I6bVj`@BE44U#sRm#YEY)gHhXdOPt4N9<%`en6)bvw->19h zhCR~}fPdaPOkdt}bw$NV?QrYLS@`e1@DG5`&atnDYnQ^^4y{cUfk!#7>^MmD=M7SY z)SVMft9d&nUN!q>ndCBk5t|GQ7!$>w*Q8<~JVXRfg7~Fy*ErDdHcK$C(Y%S7mE!Q0 zqEP!DF4kHfa`2QW7&mahqAQmZzyMJV2!bwrz(IR*S>>XbnAF z_9_2~_u^c3*Y?rpY_@O0I4lfK#nL=_3FiO#;zQg&d}6`bigN|&N&2C0ttni9Ad!}k ziK)ZF0{OL8ZlI8#)cy2gi_mjwvX8^p*Pj+3vTFXSo|srk?aTI_ zVk$EdG6^6L3!iJDLr6Bt&2&A#a%&_thjAwJvp zq)-z3<&*KYcg`c`>{Y>=23alzeGloL%Z9IkB#(_z&4e+1E5KtZ4pF-8cCD2%A0@`} z;IKpJ+wyg$lPurY2De}bLChX(o73S5Gvb0BzZXWNa5`GmvLO*;OxLUAt zpV(y{kQ&zij*QLayY&58=In=tdq>J1FHT^0hIykmPr+Ydm1#NF7$(y8y3kq)W>u?y2b9{?4#UTqm|8?0==pSgHfTZ}j0$IW!5^Zg*2n<@UM(%};uQH;NpX8y( z_MmUvW-ev0NQ4$4Lu}fR-`JQYd)i?+NLdYYY2!}YxwuP(zH6y~Du#GSf#SK|h9cWG zqkEM@Z9}EzQGRRt4iO)GC1}QPnrhLF1Jgd+W7k$@n-+&4M>(g@qK3w=1Md7hVLYcX zzCK~w?bIOxUx&&Y5V)s{WIuU(DrZJA$*r_;&*t9v(JlRf`DrG070+KJdxP^))~V_a zrJ6Ap>7hHqMt+KOhiwt#VYi*KJzJzV`f! zJr|1MEL?lOV3^41_^9)d`FE~quh-Fi)ca*{)9<{_!7n&%%VP$_>s(L(V`zE8+4EAX ziKnsd_TdHt+^(jaYTF5#5|wp0!{P`}Ve7~J1dBAKdM1Fe=J(jMjHY{IZs{F{bIWjD zSQa-xS16cv!IzaAtzq(z+e1UQ%l-e{rr#s~%j(UCf+yt4@?TDYePgNA+lG$Cv3xUI zF%0ItSRe};DK==9)VTEP8~K}5tnmFp#0|Nv8xt;8mo8P25=PI)jdCN~S5)%#D2ru6 zsKB?gUO2NhPak>ld>T^-zqIz`5Simfyo5advYcdd%`CvrYCA_pJ=MPK<^gH4$eq9a zA@ZaR%)o!%V}x)K0*5CO%KM*c-by#kvPS_F4c{cyY64vw_x5q z59oPhWoRZ?B(YmK8hSd>IH1$w&7kUeOmCz8q{n>Lkth4vQk}B3M^^$bG=qIwUCDX3 z9M9(7j3|6F`_c1Hj{3;nyBou=HOn5NfAVg8eHHWa``5CN-Va&jLBBsU%mw~ROR+K~ zmsq2dcvnK~OxYop=oH}-274pAp%A-NQ()qzk2Djby%naDZ_7kw!F%hD`c56~rNf8+ zAiDA1J&b2ML38*)9pRBra7k192 z#bKh%vH~LWhE!!$ZCi^G`ldp$F|t=aUzMAZYd1>G z&M_IQRwXzth#2|`MyYv^0|YZ}GXSkY1O3sblMDH_-a1CUSecBpzNqm8`D*1iF=F$B z&up&fdwl2{e4K8YX@ZJ?hl?un)RhHd+|-;O9>&(K-9F@Pi*a-+F4x z41ayTZxRh)LX=)t9na2AI74(7*3d>`HY~vGw$oQf6FE?36V!$B6y;NdGO|n;t-^83sK6^%$JVKZxUCv!ruC|M)>`{8;7| ziuO1eYW?wHVUJ7=EQ$Q^rd7SQSN$nrRP5<1?Yqr^>(+e5@5RMm)Z!mX9ePjH%{yK^ zof|ok<7JW#$%0n!oBK@%;utF#=##zpy38>#^R~u~w*Oyg*ZtMxwzNYD5Q->Onjj=V zXwo~1D45WD?+6NrVgiB`rK1Q6N$8w>8m#J)|nbVG4GkRm@5`FD{hfC`^XQiAvLs}8C*M9c9{Y4#? zRAh>+2a-T9Ms-sPC*oFm6XCA{q+9@cgjbKmZ0c8*9KI*Qfc<+%cwe?+$FER=bGF$+H7`E+2@&cceKUmY66rgT3+g}S1HCJ4JCemOH}?}HvLR%{9UXqMKReniSX>!}jcMShxn z($ViCVQq>fME{NdN{)WmNlCB#TfyW=ajtC#K+tk6Q~>tw-BMAW+f)PLQ1@*ePz#bg zcMG|~ofxGfnsIu2-DhPeza)6)Al9b)FK_!ZY``p3=^|JS?^ zo7eto_*i62x%_#W*P_7)$ZAI&RAp;sG-+{;T$87E(2z;v_JqF~D=VvNH;bhiEzQBo zvgW15o1D6e8N%`U&>b+wU4;SY@^4mJI{&}R12O;2N;80eSSgmQ|NpU)7}HNy()Wkr z3XER%VKT%lCDum@4H6QO+wY6a(oI3ikpo9|=Fj0p9Qka=TU8bP;NsvCbEb);3+Vm~ zE%UofY0U*WwW8Y@;AZ#|*|(Ca5nEi;zVjsQ%stfWF#N+K*i8vAe+fe z4`)oKXWVJtnM$;28L(5H-p8vT+}d||T2AoXeK#I^eQ!CA*$J(__k-tKZ*KF=)#S!! z^Ff(LWS^Nsf?94E>B>O`Z1qSCg!b##tsFO42S^TvFdg4q4psD>a=L{jMue!VXwsikE0n6&+6yZ#{b=YCdE^jUvbQ6Ms0cE6pZpI%zAx1cma5$~Ei%cMS-)r=y| z@n>=!ez{ge?*(EJHVR9P*M{^l-Bd_fOp$7)tp;DkSXQppuus_!C-T+1>7j0V4i*BW zot&QaWac%(cQ#(_RC(3&YtBdqeqoig3yeB^2b`7XIL9AK-EB+j$dud{T4mVz>b%hU za~rf(RaB3-698IvUBoy!#$(EjJ#cL zfZopr9%(1(MqMLImp95Gr(EkK1^Eai1y|SLki~C<>p{0@lPO$!*CNOH=s40Jh(OpY zgbi~vY}i;TYebZaC7e9QUW;z1qA839(Cqg%XK>2FPTv}INL>OOmvUCVeWE487rijq zI3-X~rnxi#5e6L0`$wVo_U04V&+)fWcJIkuX4%4Fnal|2l%;NSB|qB4b)9x9fcLEdb-KnUTJR7;9E4 zbnQ%5e@rH7<*lEAg`uZX1v(1Vv(>}p#QkN9%@7~i6oGuNL?;|ffKyf)Y|vI5%tBeO z%iOXu*%&nCluqj32lx zcnUuubU_uqpykyRf~_C%-OivYOTMq;bSHxSs750PmE@jntvB0gFa0cKp zUBS(P8{0!pPAsD1-%lAf5)7}83eBo(5rquHOr#nnN#PV;`A04I%pV-6&IamvTZ+&t z1*B_-nl{jJE1J;i1Q%Z`7i_%bpj~C@?wvgHv-Y5W-;+1=(y5JRUFl7sNeWPhC&R-8 zViMYgeMhT+aD+?h+=(Kipei;ifTNxSYFC=h)i`#w=o!~-SY^6TpGJthAhWa7lR0A3{>MEmt%WyOOx7oLvDKML~STOGXW0BPN2R~#!ubC!i814lc@ z>$@(=vYtVn&&a>~*lC$d?)1eT`sXgI1UPec6|6Ot8Pw~ClczsW-E=Oap0~` zB1&!7zxHyUvhEm#o_?n~p0|+H#R)R-QpKk%SfwQID#v;Fh^^*lr_K>o8xdP?;;*8P zbx%HSPw-j@$Pqb(@4UcWnPyv*_b7GfaqHeYsh%8M_3dALmx2r_b}qO)?gKK>@I;hR zHrNi2VzdFuAKlp4`*Eg2ALKfj>j-VFUp^hYC1mYi=KJ5o)=97acur`V{9Z6p#4{K` zSUO(t@**TxYOjYFUsRwJqvv0yt}ZmV{8fKSw!t$M5Pm6G1VVjp#xT4m_P&a8JYz|z z+KLkqf1}GJtU2Z?vq-!Fs=8>oQzaejrCaC#jN;LWH^{ntt9EJVi+uTlGr^nOaW;x3 zp(Tv|(3NxIo}~*xUz<;;NH4waK*Zs}yu#gyT$EF;@;vDpH(&1?=RAdt?WbL0f4Sd&IWb-CV4=(X)1y-rAH)P98u2!oOVF_VtF_Z6@e_+q6Z(V? z;s`=Jcq3M=K0N(y8seBFOFe=axpY|v_vmohVRR@chiW8ke=WyULQoy8! zA|Rw&sN-edZvp7_(Nc?6Z&_t4|`%+2j^;hn$JX;Q?42v5Q_^M%CZQfEh?c%5EoWx>P^kw!VL7 zHp=r>o1M=wd@=taNxx`L-f2e6%vdm!Jp_SJ&APGKJw|Pk2X#NAYc=z5Ad{wzi~fQ^ zTMH@0jF;~JK~ckLV8j8>=Z3|u$E%SZ!sN@`Jb8e{q>yuDB`MxFzWTsJ@U@Z{k`|IY za!bRNQZrWrSOICt*Rx5I4LG~qp;Uwdck*u+n~Ujqv*laADe1UjoW}z1k|kQ3I^)^C zPBN=2A+JAyIf)AI+c!w;4gehoMR>1PO2Dr|KvmAYN~9TDZYkWVitsHZ($n}4j-ipj zr5b9+%+{nlJ-S3OKa2I8N*kQuuC>n|No#$I%KPy6&4QNSr(@zt^#S%$I7;WjCmFlY z^-N$nery)HLjV#cGrPDALKoX97-=fLRsS6zhmp3{*CD@x-#UPdh8tFy-5E~9EMnVV znbRG8RzMpItIF+pc3#F~p}&d*KaTeJmm2E?Ks>up&6I}TQ^AIbVWDZ)i**Y0ST84%%h8vBZP7L0Xt~l(7KbNi zq2slKbLE%((&Q{-7E+{LO6Gb>6$;-pQtqPCJ$$_#+CwPX=_&Tz&iAVS5TxS!TbEq! zf7`$L>m%RN?|9CBw8#aIB!k74SqJ}wPa8%^NdNXEPLL2-M)^9TXr#aZ!Sq0xC6!Wl z2n_dFl7afu(!kCpoEj6&S2@FgGw+4M%-HWN`Q}Q?47LP<*QNOU??i&-pKX68p8eV{w!N~_VU8YFUGF%EreJF**U_7n zXF&4lc%CCx5Wa(ennc3$6dA%rcBe{ID+IyDZk`-C2Ylo#RNg67$^;+xkW1m#OdXJ< z5L2kIW!FN{9TDnhBI%U>n2Z82FjKx77RO~1+@gqc7@!b$=%sgb)gLj_`~as{jMPrf z2VhD(0wC@^BJKUL;>_waby(!?Ka0;rLH;Y45B-k6Ofp`ajZh#gR{G{0{yynUQmzoi zVc|$LJ((0_YymEjiN~Jp9E=q**cwEs^9m-gGsd=nb>7x~O~qWC@NZjF)8nrdnk=EM zIOW$^^B|-LN7hSm; z8oP2?@cceuASnrAz$WYxJ&>tzrSDoZN5`3A2UsE4uBmo+y3&P^xhVO|I-=`$JUmAz zo4<}A>yKTZNU(wH+chGD#BtbJoNY`Bv-_A&hzUQzS0`!E_7-llJ6jzM1`VJJjiPm? zmG0-7ZfO@9a7g%)rskNHt_d#=HhDia6*kNreyz>6(v-N1X|tO*UwPB$v&%nGj4rbe z3-bG}c3yGcu_LnLf&>f>A@oMEQ&_HaRLu;C_Gn9NWiKRGWq94apx0EhJzhjo1fluW z!j=AxTW9y5F5IMB(?SU2Ydt5mL-5a_F302Van6G&5KsNjwn(mI>|7(o z?pzOn%~P~Ki2A!39O*sPL1mT1yx<|V+7~aNld0Q?_;r5C`91p=tARKHB?QsBmdQ~1 zEk601Pfg3`4-kAR>q@$>+u)E+sCJ0^Pc`IC=5MZMRB8{Icc1QDLAs^j7>_MeK z*zC32Od02eK*j+`dx^*t$|Bu-6W(5Ys?TlnC|E_nw~}~I3mn6cv!+ebu5}&FKde#daWYnNq{4Zt@LA%hGusR>wu ziI`N=b&!mkrj?B69QMiKx_-=kx00{WszQU@v$rm2?ifOqGITkVBRS1hCQ2A8ob8Hy zJvA^J0JWo%vxfMfkRlYs(%7*r(ev#+v$uQi_ZOU*-OfH|&U2pite15!bg9dK&=1#j-Piqfc6Bl{50inIpr1hh zhYuwX2&|-phN!5kg4NX^C=>#PRmb2^cr1d5fm2aXCQ^-uP}7I2N?>YIsHz!M%>t%o z4O6rIG;lRr#XxNlP#YxF5(72Wgh?ncLk3Kr1=C}13}a{0g|VW<%%@4 zjqrR^ypB0u-%7*K8V8KX3MaM1T3TRi%`tYS>h@A~MXx&g zxUe63aO%A{_k1~bd^xv#In}pJ!b=CKAF*b3w z;yKAFjt)deCxVkJ!PS-M?n3f%B>Op0wz$%_xiQ1s*x_!xoo)sZuBMUBt`YV-BdkwG zm|c!Az84{U7NOr0sXw$!egln_CGj3lnZhv{yF>BB%sn_H1v&}mnZrxHJ?shNQ{`NlW>R8L_!{$|S zrj>Cf6>%mv;*GB#m6RM86($Jtk_>W^^|I1*&u8jnTo9bg<);_&Gp_J6%LEszb@T7* z6*cHzZ#BI6Omw?j@}SeW;hAa6Ba0^wtUGVoc3+kC6v%sX?D}%-`Y+lKT(lp$=rD50 zaV*zqGS6u`-+89MdA87Hw#ap^$ZfuO(|obVT#3)z)u8#Z==th%3r#hPJ`J6TTk%egY0Mu}iHl|CYU}P!u_NH|U!; zLq$f`QL^yzl~k@4^-sfEjjQ*=_mb1R50!_WvW&EvnspCo*gLg? z`~nj2MVe}v{JcQ#gLAnM3HJTK=?8;awh`M~qjrT}jUHh_*QqmKUw+x1q^>GU^M)nM zNzYDGiR~rPwFFE>q`~M{;jMqs{e{}v@osh7bVDX_N0j;Ko7lTEx`ECiw4}qe=OI?! zAKDGmniChE*=FsTmsdWd%fEc0CQDc__?{Tlbt&u|2CC#Tb$G&K%Qe}429c`XIh?{sBf%f5Hs=|7wbB(c+eR!R%+E$;L8%k_!UiYkG1r%?^r#+xCK zphO2>6&+?bBaP{Mj$zxPIq9N(EiYFVw~$)6UF8?wEQ(cr^qb<{Rkz-h>}`Jk=E}j| zU%X}i{$)&8d_93L@V|`WSO1G2z~2N>0zs9P5MX7r8W@dKL*w9B0)j+E&}lFh4aTKI zwJA^mNliynO%JE0hgCDcs0lG@Mi@0yG}Iaem7}08NSG%I?uUYJQAdPeklQqnTM4KT zDmsv@?k~Xj8DKml7#CxVqY1!0jLZyUZh@6p;0!GQJQBGUWQGNuY|hcL;uEd(3AQ4F zoiV||jNo9c>1aoAb0K=TlYBhM{=Vd(AaY13Wm_aAbT2LHFn!NS=Ke%hYziwboqgmS z_gJR(kyL(MqRzp1v&h~4f!+r;S)G;Z7g}i7m@ppcQ@i;T0HXDm#2B2sT^ zm;Ow&{%nkbe)D?_p6@k$0WfgCaP^?@)j`pEtZ4m^==EXIn|SftBjVR5#jEE;OU0t+ zm7@833LegX;$iczJY0GtUVS2Y^~`7;fMJL6a)w_oEX6Jcr!Js~)|+m|ahP&I_K9xf;D^;YmKK2*)< zGL;r=oH2n;+8K{_PzLZ9`NRQZb>%mn_3zBpQdd4I!hNqKTds01kQ@CtG#|xPf!(?G zO?g~2X`j+DjDrBuA-VL`cAi#vV1tgbiudt1dWr1~KB%`^o>zZf!>6-Z7TpP7RY(d# z{yNSVsbH62%-@lO2u%-xK3eeW4Iy(W-( zXb?K03QG7E>ri?Mv^C5F`lq_4`IrkY;&T#bwwdM}c@b0>dN!j{Rizgv)bZth^(+iNO8G4_7~mXeCHvYILcrUr+qqE*3Iu!;s) znE+8CtE!NpU@8hi$Eh;#Y77#LPJz?u2r3gvVWYLU7?J=_&?Do8Y@CrU&P0SWGsan% zYgpT8*vK{Q9f__^Bo{lfi#5%~jN>HKwc`kF$zmIVku~1L3TJMKwNeN)b-B5^ote75 z8QRVSZD)*@8=~a`l#GLtQIYl}gfkxDiAVTjk-=Exb}TXsiwfVE2n|#e0Uble>}TPQ zbMdL#nwPnRN-n96O=@Lpb+X94OmaVqJi?+(uqksJ%I<|W?FEmv$fwU4Fec1dgASZ7 zPi~h#uPd0}AF4A3FlM*@w1P2z$IQZBh16QwuTWbG(!`2h9}>MeBzk*T{5DPk%xb*p z{4v|%1h=-Nz|!-FQt~nnm6QbExbIs1#QILRX zlw3&=7MwP?bXG6>oK9wzAmgGS{emDZ!yx^Pr9Ro^0a@OVJbzeTFe)z^lNXQ4OGf2aM`Wc#wpIPs_j@c`I?TGB zNC(@D#~(?iS|qcNBnz#Q7j2T|#|q`R{>4jp;Ie`>!K?+hzKu%i>L!?dxvm zS3Q2KeLG(c9$gyFS{$!=KJ{o}rh8#-^!fAWi;IgdUc6XZT3TLSe);m{%F4>>>gw9s z+N)Qu*4NixzkdDSE7|@S#waI#m;`~+^fs&8gm=e&LAsFJ5D@Qu{8HtnB1v>`<1aX; z;bK~nd%3qr8P7r=Ujq+*m@37hvbWZGdne;LV1`I;#P=+stK|P_c%gQRf%mBncw+IP zqwxOj+5U_MhqC!n75mK=al3s4YT%I@k%D|(`JF(B;U$%*WEmr3JQQ@WP#@utP7ex? z6Xd2RB~K||l9e>3>SVA|bc~L#g!A%3e(e5ShGP?ywX!VmF=f@Z>>Duv#*VEaBG$^x zyFO;U5Y`>IaLwEf%Tra68)t$^?O(5?)U}e7?5;n~(d`=>4jMas;VN%p*IV~k0Yt8v zPfEhP!+-4T+*4_q7N+WNqH*d+R7&Y-<;oT3qkp><Cq<``_3>ArLm4=vQOq{ zAIsEjUQ~-03MHypL0KK-rr9J1-=5^?k z62_nvtIL`5#EbiMvvzwBziXRdFkE*$N~SZ;p=OVD<$m*uShLE*W>s-!)$!)Hk6GN^&|-C`tN=OoFv<2ws;o1^t}oku@B%@ar4exy`BRAJRsYu5He+T0C@GNZ;ZNyC(+VcMu+3K-+Y zNt32=^X74@mNC1wQJ1G9-W?+$JtHv#Bd5p5@@A&X=jUo)JnvX}IrDn`|IvW{F`Ci! z{l5hzrrr)@7>)k?eY7fwS+l*eyQTj1P!Xf6_LBEOyDK8}p4JqdsaM5jn(%;ghk1gb zax`#=Wgz z)&F6LQe5=9+Szt=GCq5R;vWjzHgZ*aNIN<7IXB>&3azf*1!aFPE>g4^V_Gtd$;`L;nP>2_O&*7$wEPm6YJhiUG_cQcVQ~ z1EUZS6j~L9QB%jm2qZX#45w4!Od6cYfYX@>8Vf;YBS|bIfr-+fp;2TEoP>pua8ME+ zMkc@+RHQZ=rN=`X>Y+tKbt5B;i7Cbmh|Fwp)^ZISJG@LzaI_=2*pr;Bwd_r(b|Si5 zk8P{Xx1#G=kPOW6hUPe-DOO^Pm5MQDB6Vva+7{4lVziwUZEu3MH&u)SFaQt%8cNk& zjn&;H>Rw{ZW+7&)K6W=BcaV!a!p0@gaY;0dG`hw)u4c9lF;_q=(ji?HkV>^lFM;*BJ@d9gWbPh|-&lR$yy!!$K}^XuB04Ogkt9T;wNh_byKS_Ne%E zg6P#*(dv28a*I%}1%(iSww`xkYYC3DxoNV2aY}1xv z`z#&MnhF!DpkA&bSR23WG?C{#k>@g*?>1HFIbFPY=IZv@^1bu75*Hh;yzFRvH9Gue zapnE%|Ci|OkCBUG7=-(#ZfBn7wHtZs$0XW`xVu~9-;q`~l|Uh!ieHj1hi%Gt@T`+e z)G1R;G~r~&=O6fqW%e}X5RiC;ywDyffJ9Lu=jywX*BmT~h%WDrQwyvpuh3vMa~Rdogq^QsMP74mw&8$xGXaa{qA}aG-gX_=nt9%sH^h6oF28; zV&cr<#g9yH(6kcNCls-tNIaHC{mF%(Z8Jj!J?JSoFKt@l<)Ex0YP>y6UpM-Bfg;LM znJeON`_8hmzsKo4XivW$krzY=jk3DS-5}TBQ?dUI!~Ph(0LlTOsHzf34M@Bc08>&7 zKra;~6d=YR%6L^3Ej1Me6wHM|v=IdE_ zSg0mWjev&|HDMGYoK8ltX$WmLlFvsO7@)->bz>upsRt@Xt=9P0l1Y zJ1sYBEmup5vkBE+$gtDp$T$L9ioOlO&{{)mg)_Fmnwnv(fT+w=U9l)ILn}tXEwn<& zZ7dDkBl*`V52$MSSI!;1$&B$OQPbEwQwoKPeMx}v9h%^E>ZD$bj@N0 z;R=IL${>`}2sbF2zyRo~q!4O0xYbH0^)Si93T{!R*i>Ni0K{XTmJcs^^p^tq3j@Zy zF>_qb9(3dO`tiDg`29O{2E%nmB6KHr=>ZmXaj!yj0nYTF(It8dplc%ldzCC&%@DoJ z6D?gAy|^oS(I8%aC|+w*__#O!D|F3toAmaXw+>j|8L}xKvb{1aD^PGtmeXUK)nRk) ziA`#&O>&FPnP%H#_456-uHkomw%iKzs@m>wBT`nj+w%GzX~}-0qF7OWyzue~gPc?P zSqZusNjm3J1n05^8JGFzfF;0Her7p8t3r@-OD7N51JvtZduVv0O<4I@bnBVqZky5l zW|O+RrVSMq%|(_EFIlx@TD7EGx1?IPqyW~~=5Z?EkrlFSBSo|SB-_R>J1PLS!LZ4E z*QtU{Q-wa$B|$USqvxs}q>8G5qHF%Xj}X>Wd}tAb(T$3g=N$u)#18yDIp2 z_c-r-2Q<9XVfVbRHrDr!)sdp_dRD;F7~Thm_ZsiIwhu&zsKJdZ6BR1LP_6EB6Qw$6 z@Q(@ar&)Co=2bW;puQ53r%5{G%{8AF)b2c^Dm@>t&p&y~(*CgZx5+Y0BF0fE=i%LU z#uShx1$+*chYjqniJz)$cq=?u8}+k_i;7$~w~QpCRuIe)fIzeL5)uvO4cXVoDG^GfXG~v)P%e5HJ6X6RsRad>KjOqkxr+ESklGi1TUOt>)x%H-wY+-CIP$w z@!Qb96OKoF$)_*tFcyW3sf`e>$4k3AfY-fM&=;yRw1GDugqw@m2;TM?0{ZL|;Q(g| zh_g3`MepLp?~aJyo)E8PD%ke?wrHX5Q@$@+e5hD<0-MgYjc^}WcCI`#T5LBS?=mg(tQoMmHfUQsC@UD00hxYwm+iS{wyABlNo{tg+MMDay2d;T*xnNE+p^#7(GiD7 zr|eqK$Xb(SPg3O_=K$FRfXA-?f?_KDyk7n9+FbrI`6ZB@B7YOR0=C$TBxU)$wX>&I z*_8`)#_j0lFl4>b^lIdAu`G1y+ua(~p4V+6l|OuwNtn1|j`-?}0Zf%|l_xFucymhF zL=Ctaot2~NTjib=Q3DQ*=gp=Emr1{Y{~(xwpOUTb+;c`+;W*@+Ro(rp0>1lXc7T26` z+!1?Ym(+JJQqJz~MCMKYrEME%pp>(D$6_Ep8T~W6ROVxKcxiV$Yt|2vy&2baI)9?~ z`bm`zQzppUY>JYco|-=&ez8S_Q8jMteHr%@d6)@W*hie$@{^Kx5_^}UvMYC2rF_AE zQYn7N=Zcp2=~D-C@gI zvD7fM0y-F)I+h!&Mspg~j7c)n)-=;4Sm_gGLZY37XfGu?nh~9?h^}&it25EdkL0(N zv?W3-WG{I~EH&aNE#?&CKoToHg>xdEdn%2Wlp;t?(#=TF&pKgv@rdx!VWWbB(!#x_ zg)!zuyR3>L?TU7|6$SVfc?A`@gci!9@-6pXmLA9w9ZEGgoS++bNN{8)_n0p;K}I=a zKs?LDr&2UhNw~8_Y#I@Fj)=QJ(#Y4smr^wEQHc-fBp?n0xCKB9NWmsIlo^nP0pje_ z{$ObXT`zU$FNDl#3)YYmr`KD%YYVS;tDrwjXIOzQ#s1(Q(Y1O&VJhDq61@W!28YFO z;zX~Hh+Z8Rt)3Pwr3s&363&(hXKIAAz>cy`w9v58*#LHw8yH&xIva}Q-Ds`d=v9Z& zt4`yUPUGiY((xYCj(*F=A?sTsva(V8D`SpDV=nolZkI;AGDiZFhjzyI#qaLU*xG)@ z<8i%xYrAbrpXI|*(}z>i=6Ol;3t)_z=Z%^sjhlz04|_~o+RR%YShn4;dR%PXcG0>m z)21!WrZrWew>HLhLv_i!GVS`Z{vj+=I7fx>`qexF&auFAs(8!vwaB^3`1yP17hCSU z?0>X6Ht=fx#hbPNS&Zqr#CCp&WvW^3LpRrTm9w%om%HcTq2Xg!&K~$N@gU6a4Js(y zYW8uo=&fg(aHnz&Rxiu@idp=(n+Kz1jSei|^)Hv@ z+?!36DrL~EgJV=3GEFcJ-}HM&+)jkRC#t@5%O46bLwx_S+3VWN_Rqh+bmGRuw#WT^ zTIb3Z0}y<)=1bDKo{&pPZi%M?`lVlaYUcEoXA%$e+kH20Qya!=>3W`@4m23 zo7d2>5LD*~v;#gGs8pKp{c%$wJ4bdT=XCz9K7e4Fa(4)Xz$1pAuMl~w?#}uU99$>o z)8c1cr>~_tPe=ne$KL<28lf^0u()q|-2@ZjEQ<4WQ@uBxE5lY?>JQ9ib}H{+WrN$j z7w3;P)nc?fri?mVRjgp3kL3mLj+)vAy{N67P%9P ziov1}XrSW=m}6SllVt2s2JVnHZjU}L(nw>cnMSxZKFkik%~>;S6Je(}F(QDpGe~RO zR*L_2n&%F>TPVXNl;serZM#F?G}KhQ!&VUD!rbCZ4-R2$52tS1MGo1g6|!Gz>jAB8 z2LWhN!sDnBN9Zxf8T(H%51nGgon{|RWETfN*_0vn zM*jj}*gy0y7B|{$E5G+Ic=Xpi#%n%fU5D{XkNMJwwP?keci>KY^2P%NqdRm)cj^LL z(TQDp8(UF>g@4G*K54SQ??dB6Z{x*p;zeu6L`$cHb18;n7Y+JL^!rNn`)=s>Rp<{? z84TYx9J^;YRVSQn6g_VdF9B==`WKzXZ+fI}dQH~*OqTjhrv@#$#%x+9<@cr?s%M5HyYmz^i`oPkoc!F3Ym;ktXV;&;cs;fDcKOZw|B2B4B0+CO96Nrt6v^ao2UV=A zm!(#EmkS<+GRJSV*Z9!_p0s-3@~i?0#<7skaJ<_1{*qK6HT1s=C^q~&8&88P8@Rw< zE4OcN1am@N-|Sr=z`%3i9^H=`-WK6`KDTAwT|5gU{U=S5l6!-peY5_iw4l&S&-N+7 zCcD}m(rw+PyKkSo)w1M$Sy1DNC>xY8Rg$9;!T1)=V#v#!uz^82;~zf+K_ z2^DxHDdHkb&>fG|(oiR=6EqME43w*` zx)FA%>H;M%EvP;f3g|2$6(%9WOo=cHJj@ygldHoVP_RvCxSu*A7=sMO0N+{^OpC># zk7#0!5iuvVut#awcnl_!jOlsY=SwKL41=fdg1_)j-M&l$C~M5NAq$bdu<|ldujQ3Qv!Wx+kEMvevF6! z)}F1L*eFi?A@9H!CvM$3LeZtB? zarLmIddRr4&-6x@&6O6H%Xj?Gl|&p*JrNUqG0@|NljNRUx6z9G$cXh+kKW0p_Hrl# z9Lg|*M#V4p&d%f^v$PDR7x7be$=5n=Nvm zE%utdvU&De$Xpp9yN=G^%YNQ+bE&I+WpeV>;{UXJyhtGSN5^`1(KML+n0}R4WiYuO z`Lx;0XLfENy%2JO*5y^SPmK-eo%~K=muhgYcWBX+_Hdf*S!Jl?d|?+Xv{w{1I2r8) zLPS_iR%G~OgOzW>D`T2i0r$4X6V`POCQ&>JnU3s5CnZpm-yMs*50I}qt1L^EbIJ!s zl#{dwk+;7gz6h#ZfKUK5SFmM{GBFE;v5P*hF4ma%WtH8AJ zP>r&QP3mtKIMpq&=JD4Loy#NU9)1!j9`8klx-|<_E^VfmV2*8l0CH&B%-(eU;|<8v zqFv}4eX~T>f#-a~O)E^f;X@`wc{l>*lM$~%2KaTt`4LKi}tYY_9rP}KW z${o&On%iA+nDRJ-stgVr*$6_hHuL=VDF>xrlRb~+=cthPn|Y@nxg>>gb3a6`Meb9Q z$&FsDM-EFc-K=UOw#U(rHJ1N zwoz%-ss69@CzJq|sexfIRU}+h9ifIo!4Oy^0s}0vn~pMUu6F zL6LdtWIk3)ph?oz($r%TgxVxyU8<=8+e}Yj!Z(z%CB`%pBQ0|yyp<8w#t360R<|`o z+3KTg1d15UhJmo5AZ&(!<22l9%CDX-orKHK(#WLYv#IzT8vZg}vye%+$|Brk6K`|1 z>a?{UYLg#plb>?Q?QC)fo7}}BceBWS8`TG(D+a6)0lT-rp)GS2JyqZY;O`~XPd^6) z)9)i-tm`sf88Tm(vZv*ou}#_oe!QMwe&2S%V3;DQAK&$Br3|oyfOt|MY46|5WSF$Tk=*FzCOo-(RlZcT>N=%3z?{VCc5N=v~8!2f`V^DmIH= zw2BoD@tF}Y%g>DFpBYa)lXkV6HguR*bXpd5Szqq5$?3AW_}g6Qvd!)U(tO)Q1 zWhWZ#_uX|0t#J3g;^&wjXq~fFdOlQ~9;%-d#y=6J9T&jaO1^(qOWJ3Pw9`s?nN|GkJN%3H1-bRWw+Eeqhk7NC z^{=<3jC&!oqiNzxwIBe<|?)p&0BUVPE2{Sdki0jmiHpaOzcAuGEBle;lmyn-ueg zRJ$rOT4EwQ!X$gxq_sF$L%CNONOIL8zq}kAJ$qUwav+bN2yyzEYLU0=oN|&E#H}($ z^UiGQrg#&`O8Y3w{?x~;{Cbe=d5zy4lZdF1x!J3bqj2Lx{b)B3=_@9A?WgMJ?JE4~ zaN^gr7IiR;pW0T{_aK%g8irMfu=60n{o0 zWgOiN*5sVIfH~z4dz0vgG5c)Zx%3pgCqFRe80K5MGX+Wo%O}2seZM_H`7aWWRM;VH zqL&A?UJa>YE=ob>%N?)Cri1vHBhLX*mQGtU%?XcNh=(#qXgJ5r!a7LezLxjBS7aEg z%bNBkDe_#oWx_XH5FB}_FEH&z7y+cW*H;5%hs{?M#!N6OgZQYa-@5sq+F)<5ssF)L z|KC)a;un;GkRPlJfhaT@0-*{+!C+V?WB)(5TWfv>b4?`l^AO-!AXrZ4Nb^=GbYnqn`)`6Wn)OP6_MmdBs&w5 zy*bgrnh4bKom@1XJqT{TK$uVP3MBdjlQwVPsD_17cZAa;cXIab)ZQ1y-?u~e;C6$( zTa9=7TkiC<-|p-hED!KA5A~G9xa!9_@K4)w&&gO9ZI}gCjH_n!n^M{xF||RT(k7sE z0A`U*?%Obn;~O~_U=Uww)Bg#4zr*g6Q{3R)vKf2Ui95ELKeSaayiuwEc9H<&p6@kS z`iI0GNWK(pS)i03rn_&oxu(|(0Pd4PyrrPqRIPBX zQM3r$EIpR2JXHkv%k2uZjXaY+Yd3w+VR5a~D!^`kVKwp2yAzx|vdBm2GI4;0E2?FvOe<~*L~I+5==Q5ZONZO?3V+ViHGmHx-C zCTHKR{7;lBE)rZ@u(23N3Wf>$Q%qILD#B8Te#)IV*vEInXPBQ~!&SLk8f&dV^Ib#1 zfwgS2>4C$mLq)2)OsK(lS(^VHiT=`pMvVNHGAo5OUE@FV*XeYBs25 zf^C;Nn2D{K)eX3ZikH0C-BMQM6v2dk#6T~hK&KXzRhiw-TVj{*yL%7RS6>?|v!BYs z2y!Nqr12BssMwb;ZA)HQS@9dVlZx=?hn+?Pnjvs^CFT`=cojum&SZmiDq7HTn3b=N1^;M!LC1rWm zrR571pMxV(JQNI(JX#?7$5v=3&&oeWSJF?aJW|`?5Nkl14ogZ9d4r7wnk>*{EGH#? z9`(H}8GhQI3X043haS#O>}I*31=}e@C$e0EGC|*x zKqjkTj_;rRj|4?sfqmaD8hM;Qsi=efqb?)=zd-C0*_4!kvc4kHhp2(o;A$|W8cZDq z!y(`VG?I)%Gc_?RyapRf;HZ;0Xfg*$;UKAO1dR!&)8H&Jj7Nm&X+k9$FjFkd0t2;0 zL2Z#xIYObj0J6Dm92fbc5h3cx9qP!ieo4yL06IO@K9bss&9 zrx5ER#@ZWU?4%f(G1l4`XD-DXNlE%9RGuk|WzM5p=~3lUvXiZryNi~myOy^%+1H=q zzlFLbkP#Tb_V*F^c}TsT-CS*V*qI%b8D`n=i>71ii;eIt{7(+H<`7nY?@1yn7dTwU_t}d4fm9 zI_;%;y_NdIcMK;1jx~v%KN2stNnSiPdhyI??wRpuyR^N-w64?QMyFNbKj|~u3!S#- z+ilaI%98+yJ=(Od(R=&7K;P=^PB$W~u0@*x$3;bZjSBZk^7o4_A2iH4q@R6E_uOfI zN(wJIgL@{MbLtZ3bUx=yF*o^|c4|2QntP1=EDLMn(4~#r3n2 z#yO+rIpc@3#x0Z5mSK~Y9@9q;%^%&dY`tpLdcnFa;}g(i9T@=A6i&3~ydwP{$aWaI z=s0>w!89Ovo7!kwOc(jiTnU@6h+Dju{j#lLeRT2j^*3LA{>P6h|DYz@QWg|@u%}pq z34@alM`-V!QJGXY`>7>8umNSzhoqYxHcZf(S{Y7rbO^%zoTPb2 z-K7#Twa~cYmxOHr`&QJb=Op=5E7zQm?$Nw&747u%4S^5SF8(akebbC?KoaTTHNRbk zMAhW3iYMDzr=q=*V;A$ zJobz%D+P{S!k@*qui+Hd+DE;v-s#gzo zi%D|T4)Ls}e8-3*fi*W1PsloRitv1QWwEK;7n!lHAs3d_u|XK{gj&$KFY4Idc1p55 z9~Fp6U|2fPz7S972mG5RL;crqril81$^}Fjs-^;kLeyYtP$UeFLBVnAa6Aq{0E{Lc zL&M>j7!n%|$TEsHk_rqU&Nv7<3(jD|SyUL82IJG90tQrvr5If%FfaoqOhSj5lVNfq z+#8SB2Am6RXfi;S{e7^gFbz}~9vw3Dl>vWXsBPol#z6H{!=NcJ`)CkKL? zGtt9U%g2MV#hVu5&DieF+TqUM?kWkfx7lLt<15|jC5m*{k98BAbkRbZ8@XXg5DybJ=Ldn38?+r)KM;VnyaXI{gc4@2TlRe)`|i1xhZSXfji>G z8}#S*2Mc<)DOwT0c5!g0?ntEG*e<=v-HHX{@~6W`;N%gIR!79^CqyeLqJ=!+%uV4` zwQ%Z|aO$pb`o3_cPBdFDnr#pPk-K7=#FI_pfhNh*7UTPErZ=Bj6t!FBbz0|k{%R8e zgP7GROMfO$e&P_{;vQAMdCT3ct`$4wWlxsgW zY{Q}={lc4i1-Eqb@9E~(>K4@N7B=aXv?`q6jb4L_LBr}{;q7tZy(!Uy8FAgLxPDsF zFaeY#qz`*dTc4OeuCsbpVbgI%)|DskxhU_=23VtL<_}!3AKW4iH;OHVD z=vY{6jw3Ob39)l=L_qCHk)c%J)JGcxzaOJ{T$t=uG+g6w2wZSAg!?JZEoXwRlGwL>ewVqg0rdfxB563BjUA~1G#g-UeI_HU#-QhDbO26a?aw zcAQq?&C77Lop~PrqR9?g;4y!wFZP@M0;#AdsX|oLfW#jPfx-cJL1Ab(6oZE1Ffcq0 zM$mwf2nd=Mic7=jF>oRl-k41=W|1T`Ekhz%7f0c%Q+X&VPXRSL7r|u1IczwO4HK|o zI>4}Cy1+1D`V5!>9VVp1#8jAx7Tg-Rgd`z-h{!+<W#JmAZ~{7Sr7ABON+xALEE7j%Z| z^zGCEI5hzrYb)&F#*Owy`2NWYzK#>WJ|bQ@A(=}y>diHISY~$Xo@Hs1RZ**T?o;dB z4x60Lzo%87Vk^Ms0ff5JVO8#h=Aqd zM6Gb_zHs!OaQLop=#KDdjikQPq_W&1_quKJ75hU4E?X~pyPfy9N(+=E1sR+U(mNR_ zI2yn^yqSBzm$S!*z1y1+?nMppAp3i0`Fa3L5u%q5(bJFU6+rY3Ci?Cm`R~*U+D!=q zj^fWSJ+x>UU{Zp+HFuVU|2IM zygebjJ1u%JE3TUt*Uw2BW+n9#M)ku+bzR1FkE9RoN$*`ZxszjhE7i36xM|fiMOm0ADb}0onxl;U;QM1H3#|)?vaAdo2ogTH`%#L{Wqy}h zAuk(*2_L%-Mjd$b!nFYqWqh*KLJ5?@uw~}1O+Wf@Oa1_H)AQa5sa}_AXTgyl-skO0 zX6{+K`{)%wXT_E(F-7r)i^@q);142mD_Xwcx+ffp z_B+bNnu#Me^@I>)I`KE4Ci6~)riKv&uqlWD$zGSIZIJ7lfNQG+?mV>^v6MOWA{rFApj#zgcYd*)bjl(k0l5 zu;^kD;U z`2cvkvzdD@fLpsoyFO66F^Ja`%xezeHSgdz?G)7S(S2}4|8|<_W{I@qfyL!!%S&xm zm!4VYbo?v5ayzXH+pVrYv8ZV^t#6h-X^?c)i~H+DL$#t2093WYi8|p_y>Mnjqye!P zF!K$fxmwZOUC~^n=y|bdIZL#5R`fbS{N|WgQL%^<15f^}#EKRVipCF!>-U>x9Iy?F zb+d`{7RUP;9P!tS+pKfQkAJ|28{@@_@StyZqXoNC0$j*`E?PcLT7Di{oBg$dLdo0r zQg+7CV$LuRXRwavDj0UEn3HtD{PT~6mFo^rXt+$eYX$1$Eq{ zH^p9$*3@|Xqh3dCsNvlVhVXRX;akUUdUb98@U2c#V_Kx>#VnuX9@G79PF+#~u_b3l1EYWy5;csZ)?+7ULG0lnpLcX4#3@Ym#fUAfTr;kSrsDk!2uU1hGfXoic z?TNEM++TE;(OBkBYOWtVOd`fk$<;@jgzc zHUQSUEwi=8aq9QO$zPMQ|Dvk|5T*hFgMq7Wpy>*SL68VFbu<)XFoRv8s9~#TX)0#Yi<1q?#pC4T$_5&@fkZxF;5|8HWtg zL~RFrqXsHc6CF)dk0oM`6EMJ`bqWy&>>Mv?X_S!hKo`D&Mg&^$8xl+L_5@HO{dC&{ zsI1?6SL-}F@T%B~4&#MBW73#8V8MK1$EtT_-|=Ku0TRoLUFyXu@nGh=GcUL?(%tB% z-087i^eA6OPyo|2i0vHAu?x|*3>1j{^>uy4+TK!zhZ)t=n&M?k@wKA{IM9Ne>08}t z+q|jUwotb1pllDP?cBwTiQydDD>$)N|4fW1Ww$gf()Rogrwc(|xxPX9t`UVcF(nrJ zubRe|n#Na{9lvFsSZ8^z&H7@utYFx#c+CFFxLxU({N||a){t#euVs6?=}@cj#6!tU zvlwudGr$1qt6l`Cuki=MvAc$2cMQjF8IDyOj#U^=UNM}>HGG~fTs|XQJuU?H006>( zx66Q92CyywmIgrfHWh6!7H!ZUBW&F-y?fZIIKeR^+2d$NK*XgTTMDB*uIzQZe$b{o z&iv*v(~1)&Hxs1giAJTV;%gTSONtGO%k>JY^$PCl7S!q%H|t$()h}yTEF5o*2=7ja z?oW&B=EM#2lEwu|(}Gd+g3-fy<3}?lZDVFn`z$(uZvK7Qz)kz%Vuz7D;8~vEuE7=U zR={>Dcm&ihK8?bHZahJ@;X)M_bRL*u*Nt(TFL7VE;=OPUK-ISAH)9rWomy(hefi|U ztKpHi|0Cy<7YVeL#8_{Ki$F*=3n!$NLm9^C66PekQU+^HtBi&D@kY{!Y{ynm=n%sa z9sAk3A|xw8nsVJ5m1G3j(cCvL9(_>oi=uFqdL6?1^`~ym-d64i+0^!9+@B7I=6jWx znExU=xOe7G5Tu~Ic~&cI_YT$0*SE*-c-4TacK>PGA&=*sp!Bl=r6svxUla)x`XTCy z(L>Pwx^>qIASCm)jyuFA%Q8K^!24D8$45-fAR))~I0t8A9@)kTcHBSfyiND2H+nBF z^WFXiP;jJ%Uu~A@3Mi}oVcwtfLjyq`#rCCl8wEe>FRncmh)(H?tTp%` zWtY_5bC4?9QSfoK>*Tg~&h#^vqU)9IAV2;{0h$HQREG-_nj!of80{Pt^gC6?F6HCR z*_0cXqR27oW+GR2#i;N=nxHxb@Ph!57&2-VSu(A9|6(b&h=h~sFSU}_{$a6m;K=*Oo6 z75R;KBhC|Xz|{|M@&g=QH_}M};D7_4K^7Tk$peq;Ol`av@d`g!5S;7?&dvlE7lNA`$;*?x z*_RgR!`$x4-RY?ty~+5HtNkg5fb-U&m&|wPOZSx+AG|6(TqZqMZF2g)SxS>d=3}di zoi=#`vce%*@sJF-399V3e$Zq2sKczUO*+;hncm`kw_C0Ou^x0?T-uwN9(LJ-znzPQ?_ndw2 zALIJ1HM5rUSJs+mz2EnJp3lR54Rj4$IrnhaQ@E=s#w)vdk5kMi(jD6lY^y0r$vjaR zQh(j`@|fMVhZe1K{I&(-wgv9>c`#40FVC=>9)LZWQNy_5>07`y!K`d&9Bl!13cccU zdIhz51*i1#s`Tj%V^}GWciH0ejJ`5sX^kxtM8j8O&M0R-6sY z6Cmsj`N&2~g2|yQMmj;<9J{ig&Et722Tmez>SDYdkwdw*F&+S}lg!cK*`a9RB z@4Z@Def#pig`fDJd{vq#m8Ni=%N0q;ul9Xcv@S=pR{l!RA*37c`Ili&M<^2%=WJPzy!lO ziStjThrUyI@Q~i@*L3}+;2y)!q`SZX9`W_F4(Y#UTL+denny?y zAP8?u$*V@B`M@1gA8HoVM^!_Hu$2Njn%mj=YR4hzuJ7H{wUUMIIO0T4(2j%f?&&a? z2luey0GFobCO1zih=dt&i*}wXA1RD<)PxUtTAn%aWLpyIRW7>~fASGPBYt{SzPbGN z>_2n9gp>^g!C@c>4&}d*GSUcO!;qJQe6lD-ITaLSl2ykb@ahNx4yl7zAZjX-2udWp zGD$-foH9umJPEBsR3;IXs6+)i5y>DzTYn}A!PG%8H09}7Il3z3sMSZw87RvEW+DfR z01r(If}((g5|L336lF)MvZJ1gBSXcJsUk8!+q2MC#;T^KYCH=J#~N#7i`Tc;p$o}W zM}3kDTi4Y@+uefT`Rh1E@CSre7fqi{nm(KHz5#gu5L`eMertkOXbK@RQ#VmeOerR3 z9HnF*mt5bfXt|YodB^DaC5(bR{empR!W33f5+^@~C*Em#Aj~{#n@y^pbBv2$fO)7B zBhp$o))XJl(}*|LNaCue7^&~o$7WG6`*qcFwA6C(svxISh*JeQrNdfKto)>|`e|MD zb2=IuI1G>`hbS1@xL#fS2uTybdqA3e1Gc!Vr?W!WS!U`k8tTp)6CYWTr#8_h1L*fc zAj8InsUKVt08Vic96giStGhUBDV&$7kV)f3Hg~O<`>a|5Pi*HdfcNMPE*S6TI=Das zA9ir>c5uf!dAEB^x^J3Y8??MMYF$5JdwR+q2q{EMyay#KL2A5g#IE$VZOMQw2#)9X z*yZ&(9O(B2J>%V2G45Cf+c9t`0COAn z+AOzantNrE+dRU()X%-tW!%_ma`}?!)idU;CoJ2`YvY97o7P~vkE}S6pztay}$t0>?c-GuO8cGnf78RnDk>yY~V%r8mlNofWdrw`28Bi zw`JN^yPV{@$FF!N1h-17zbdKHKYjr(^Lf!V1%aDovRKL+w)1)|T%6>2Tivm#^DkEU zXc_P0`o0A7D)u~@`4c-AF@Im5%q0Ji3=1qOWd|cFKD@l6+FJ7Jop?yST>EtkOJhhb z`KcL}md`#iv^I(v(|ThNv{vSk6xh6@qWF;lZ0>6EyElFHm0vwS7I*OGiLiyf#8{wd zwZS}@?69Tuko1*xc^G!;3zy)7GQY%x()YDBG!&UIbL&Iqp-1IyC;r)kucg|aQdPCF z=e$p5?JN+S-ut~KEWl{Az-M=g4x!&Lo9eDz3- zmyG`IQEE->3w$K(i^xN=_QQbM69oyIv|H{t*Sn4;0gSyplL={B_V<5bK;ff*hw+v0 zU%{gi{2lcDc2D-Ze+TLYp?Wq5JyHO0A&mk6Y@{5x2cT6IRWQoRI5m_O7Nw0-B55g8 zwbZEU8dOy+D!6Q-bZAPzZAKv}fNLh546VzUdH`#!Xuv`l@l;rRG}jaj$QG8yXb}hP z#!~e)QVlXv3ua-$SeTtGOspX`k%`%(2Muj`1hqpzBQ!CmG_hws@^U2HiC>X95PCuq z)$Y>8-y&)PBgK81_9ME^98+iBQ1>B6chZD7WJ&C^Begk_uDFux-6=Kh)CxB}v77#G zH+JYI6E`m_D{nitkAUGTB>M`9{vzD~2k_R^*|rH>NVT_b0S`?Apq53Z=_cfm_7syd zk5UhwgvOV`I(o?&Mrp17kyD1pjX_U!_}Sxor1o*mc4VPJuCd4XC~dNkY;s0 z{kv>IBi)8<6B=eA55w~XPKN1 z*7bjGRwFrRVAq_3@D)gh0_lK;aJlgA2i)wT3wQGKRQ4? ztfVR3Q15#Q<+-IJlJaimyppkY)Q_F|!`A^XQQh6YK9_s1H|KGBF^F6n_p!gTLK`_; z_P=&g-TUoIPr~EwUw+}C*}jG8pFhxJQqdGge^;e3>+cO<#!oNhsKRnN=F!Pg1AV}P zR<38A)*N_iC(>VRhK7&qX)GsA!I+<;$ZJQ({9 zcdAeBAY1T{YwWA9-jyLcpv5W6{1EgFS9ysveAe-VfYrL=H7QB6C~wE&p})@ChDV6g zr{6E;9z#)TvK`~@ga~)$GG;Uf6G;mRhi;ubt8N-uO-@F9{f3Yd{meMn@qbH&ol5oh z*zH|>DkT+#uM6=mHm~1%$(j4=r=-H;Ylj@B9(ZPa(#*E{=|eJV)!=V)?lJfD9L>X**TPIAIz}WZphif zH3&4(^EV^=Tj~1Q>iXO11PF8jzy{4lJIG5r$d?cppt&VfYg?37NCF`&MLQ}}H{k#= zrHGPM23U;L>4@d*(W1 z_n!kN0x*VcTslD-soNN=-tOJxj}4mjk6E=(fhf5B`B~Aq#|~#6JJilPoO&QSabE zwY&=#x#zC}LlEbDFZ<#Ur)h$7`5y4@bFV)(ZhK;UW0}_}`M}uLv&0AUTi-+Tn|Ca4 z-?kpQVK>?&m^dw(JT96n7fl@#O&tXvG>n07V;K9SYLe(U5Vnun2AMm+QxqcmFVuJ} z*7_{gZCh#p@|L=Ct!TC9;){u%*RyxtE-iij@}FS$|EKjVk`z;|S%Gw_ig^8A#Mv3A z)!qA{zFvmF()P-gjg!tbYzXWEVWx&7I%oyaB6+kye$z>tpOvhz(W4jKknE)8_i=NV zMQ|R%&rsVk`34XDG-{sd+vssdbV7k|lyCiVGU~>}mtRzi6K8{Su9~ukF=AyM-;wb8 zpniX;5@gJmhZn8aFJ6YX9bR<1Ac*e8+wPM?UzZNhR=7Cg)*v9s+Q}*AN?l8dMDVJe zzB($fINK@*K(H2{A;l;8XheiJW+^o;raU_78v*xuEHc~Vi`JDEnVx=SS_*T#;2TWU z&mSyrvU8Q@nz!P9EF`exV5_pH&!G*2=jbFbro$94Tf>?nbM^U zbe30reM3>Z)QoOGcbQ*N^1x3T6$cA-7_Mr{+TVt6IUe;5{6I!jx2BAm*N;Tu6{Iv= z!3*(qT`|HrJt%blcjILl>hB|z2)tP^qQNWP-yxS^I~;eyzL>+n4BBZLTcaDIblk?U zg^zx3{ck$+htnAt%A}>B5BwK@@CW<_TpEUykwU{|0QMEotaXua5)w{Tl+#s)nATKn z1cQXsBP-B=4}+#i1{0YcilC>0r(@I^nrem=RkpqwpQUET!&sYR1-3Y6fu@r+L1aP_ z7}D%%`gXcTwpv_kyqP)9&P3gbjqx;43(`{!A)&*xRl;@95k&M(s%jKnEtY{vVq#Jm z*i0HGn}o^L#pG+N6={LA62uIvBw*{b)thy3-6Z?~3A!JE_1Xi<-yd{~0LaSFS<%;B z;*#dADGyy~_W;&#D|0G@ITZ%LUrKz^n{1JhYXa z9myn+!C6dGTI#u_KFH` z2}_0qhle2z0m#*3-~%M-JAw+yxCCLOAwIsS`XM+Ei0U2)FWj@gddH?`#B$`8*>s-? zv_O;iGr$N2#xdwT(9Qv#*xBoxnHJ6rP_`@V*-Mf&+F8!RDb7k62UyquFZK<{WoCkF zC+~G8Z#~m|F56)!Kk&kl6mfM$bp4f}#$MOU<9036=B;zQwngLC1@5(Z&Xq^d0a8Dh?V~?>9M{ zZF&JXzC9sa%f}N(rlp7LINOdFpkrr0NEr3rfyDl#pgDWAxHyr1P1(ZD4O^ zB870RXj$n=oN!gb3KISj)a2c0#XtOA^EUI<+gnec{Bp(2OlH?o2*QlB6^2TW{pnle zkwS6y>=nn^FPF8X;#blx&~dpDtw+u&sgBg{tzaJ8pZ#dW!Gp=d$wu253{{(tr55Mx zr=oFJPwi-A?zmmo0%wJ1N>A219Z9~L(^V8}=IJ<$T`nIJfB!mO)^8x;IwCs0h_Rgh0%fac!4vuvYcfJGv zx52(BZ67TrM$Ct$>wgDs1kwnXV8^mXmnZNUx1gEiZBj!uG4)82R$TlqlugLDs?2h+ z%+#fEy!B%Avyxh9L@U&x&mdacUTpHR{_b0h5OseD9@S8iq6yMWaDgio*%`Qf*SP5^ z_Tk}we04TC|BFDS5B6OfHP5` zJOoL{E0T3lI%H*Ss*0wbssRP0^No z6&t>akgwvzS8+E%`xvVRan!aMsf939cTmycIx0IgRd(W(qj4&+c$Gv=bgGu>J}uRJ zEwv+>m=jvqMs1CDU0gpAKO~VbV9EHEa0m!GlIaaFr5Bhwvj#d-9Nk-Hx;Jcymj$HL zBJyzu@?jA<*Fi7U$so#^6X;?p++@S|vN!S;(tSl_e@CLfla8O0wjY?xJhiv_X$Nlu zAt;^jXr0JJVr&XIIi0d6i*tck4Md(4fEHBP1c8?c;(tH2*&g5 zCJzLYz#Ik^VGpjcr!KRA*MIDS(MY}FP@UneDuY{x^>2yw2lwg^?baVo)E|x4ACEJb ziZhswGkla_1YWW$$((1qfPceyeGl*X9{ysg`NK53feiO^IbjEivt!CDeNVTDE)H5= zy>HU`gx9_Vo|e4MXS^=((p&}qV%+)ExP2Kq8(f(Mu3FZ`JFJEgqjR^7&h#18bs5!m z7}m71YA>@-U*MiQ!@F>j-+0Wl=@2lKSzIfyYR`v2us!*J>jSOKAflPX+5hR`c~8=| z1oma%3JL+SH%_3EfLbQ6(IKp3a=>Wu{0z008DKVc`8ZjQqORZ!+cO&e z@Ej~&AM!%Mlxq1-ur(XKlDNkLtdg)y(~Z|-C!21K{%Qzc0xzkitF`@b zVyC{45h9!qk%G zp?uPbicaDo*~TYRSYyWz$W%Fz9s!oV-iX^F4WBt|6vI#R_mySMV}+dEd0Uvi0?o%B z0N1a!H?pSii9o8Q>+$c(lfZ@Gj~yG*Qqo8yq-XL#&)~rJ{~s*UHiQl+p1cGTs}7e2 z^&`3nSt>YjD!^F^(1u@6Rh|q=NQjCAvNE2ER;Q_9XzFTucr`j6%fx9IX=?Hax_ms% zT;0G5Yh;DxSYeDU)lAJ*ElknYrYd%P6#-wxK_YLiJQWX?iVs62kfa=pM}=ZhAh{U^ z!T}&>4n7Pj6001gp&Ua{Nu;Rl(Z}v(spoTX7E2%ho^bruYNYe(_ zPtY*}Fkw)~h~$-epQ}3#z&sYjZhKOz6S>Kaa?X=_!kcl(*D&9Yv&)Yk>TBuYYh&+Y zZ{jU9-0VQx>`3x*AbLCL`fbt)+^n-@GjW?IHQZf4eiL`kCX4-^o@JYMoN?RT;*#Cv zP;gVYF`hw#*|_~DNdx^Jr@&P90Ft!x(}L!Ec0H5U6Jr*SM$DFn`Ok;=&j(GO-!g&3 ztj~C{8=B3)8xuq?LA-pbnKjwS0?eL!r&*7VvX=_jt2vzY4DR|~&htH-r@Prp$?UlV z=p{Q9!y1of4MnjBqBuPpoH!r<6BgYrg$ zie~-F7AR$T0^|ca4QslM&h)eDZ?i9e2j&=9mGRo{ncSG>ciu7W8Z_(bHt)P*(b-_x zdD5!$h&9Bp&izElfZ+WAW4&?N+}QSmRsY>j>IIEwj*xnN6tE57#wQH}`!mV8({u5( z?;@mOiOW}Vo_18O4s5K@{$=|!mqWy_7gfQ8cE~=@N2vt2X!|!^hg-ivlni;@T zT04ZREwZc%W0nX@m5WVxNtawsZOGj|u*m;*o#rsCVzbFP~(1p6=g`ukO6+ZvO0g9}-p%t4p4H zAYJl0<64nIy)a?7^-Kxf$7vu@dap3-Wuv7G{u{aCuw08Zf8?v5PcVDBnmI(M98X!Wch`FC~bV9_W_l9g^a{KT&Wp*8FlbuRn@C7-cjP zWi%OMGz~UqAfuVYe!7eOJcYBK#(9~}UEjxjp38Y!%vm_WnQ!3CU*SAyg>IDq*#kbn z#{dK$Fs?lVlnj9NxdnkT)`s~{M@%0B)b)hr%{$hucWs*h?Rr{pTGI6fp0hwZ!P6Gp z1Av+yn*hV2@W{BRbW~J0>T+PjBYns(d2n0UKxANFqGxxiQ|CVW_5;?}^DVCynO`n3 zyHsk@P{C`c=A6C2t^;uXHpAL(!&;EP956fsQUD{Y^JAvwr=cOd8g z4JcD?b6b*t8apW*uN2)m1}*^~j{(OvIM*KsGD#Fl!UOtf-vBmQ=%o2k(rSUE0UAc! zax=IJls@aNdp-hG4aCi-|0N86mqU8)4elS>sq)^9?o|U!>B{p6nAfG?xDO7EF7$}= z{!`q%>)|-9e%G(hugOsP+RI~?Q|S&0gC*^8co}!43$Dq`TQz?4&9L|gAZaX(BC&TP zsDukh7;Oy6P`LOK72FlOIC)pKI^AION)Xa+a@$NXQIy}?f=BdRb~jya8P;@d+TL{o zmaeM!H=IJ5e7&%TWWz-?*a`*^pWVOqd_f;8+|9WHR5d|`ivmU~|BH>7WO7XGBZ?^~PQ)AJwTu`?Zt z**kP#>@e&TIb92jOcfDKE^~R`{l-_?X>iXVB75-F!qNfh@66FE`BfRK3TUGR-NcSr z=klapDXu(6?cKr?f#J_SPla$hP$-hRwP>sClNu&IPP*oeJlZZ(s$|a>#&7~GO`MMk z-S^@3;cZH{$_o_XmfnuOvL9yTbH$mn+ct5e(2ranl{%f=D&sb!c%0w(_hw6pq%HkX zFC55{gF+EN=m)iw;5GRl{{g-w_*g_(?Ud~ZO+84?mAmH z6T<>^!h#7Q5n3U!S|N!W3`ni8J%pX+VUTiE~C17mUDL6JUVuNhld$lLoeGOFfXffpRv`%mzA0-j4C#j+?%nFnxW;Y-P&g zKIl-oXVY=twrSeF;ep_^l=57x0V}ltNeCeI>Gk60y=PyH^}cyD`EGgnf9xFKa)=T-RS6!Na{kvHs|F^? zkExRJY;ycKG@h9y9{2DZRTp|=`BJY6M{CWc#^$Elym)4)?W8y#bYZ!sOVW!fBuStd z`r>ZZ7-eZZM6^ zJz|j@DGkdlcd6^@fc@=~le9x_%tIq-m8W9Y8D)baIkH@RW}Ixu%NcatcJ?uWne8u> zrRn`W+hKZgZmQsj-(VoU{cVfj%RR%qwKTVhoC(e`++zDf($_0;=XLZ(uAM58F6sTc zO5;kvjrUJab1LV4xvsOAPC7dBqasF>9V(mpRu&LqVKI2WXVKpv{m%(jeJV!tRjxmI zp3pvh(j`gyjE@KWYpL(IBMX!=ybqQ^l}IO~@VT;;qltiL1Cp%8p1ua#}ghOo0LAHGwl9L zURoeyfcGN@dCjDh9m-juHXz4^ssI{=qHO$51tZrM3mE(zuvy#+WTUjGu{`hpDQwIoiPj zEwn`2TB%ytV0g9~EPG7@p)SKwkLF@T^5E-wTWkB;>jXkKNkX6}VM_pEdk7&oQY$2$ zuw$2Y6u@O4ASUONQi~`VCDg1U+WrE1PA(&NpFv)#VSX~ZAdZ{2(rmclxyu-k`#~qKgm(>flmp~7sO1F>6Voa8@8!)**F2eRTAJ&Awm7-| z^j~}dxKqqC+v*D&8o9a!YZrl)Ht>^!IeYhY)c?L_Tqm?$u8~}7MOG+kb{J`RSSKXJ z4?8!mm)bL`UQ8{&*Pl59t9$1=@x_*EdD$kgDLe7`5PJI=&_M22kB`282)Sv(W3f^k ze=TDEvp~{O$4VOc1$QPPgmE|b7x)@U=|*s@RBT^h)tBegl|;pC)r_!XuBFSp;omkF z3GSGvzuNYuAS{2W%QRKv=O$oJ1XPuJ|3*&_%?tS<<@1EpqrE4Rwwf@%?y$4^oaGHxB2LW2_vuv2R zk7wH1^`_^7_2Jpk0*oA!6wlW)$69W5Hwi@jtSE z{z-rtI1)(9$jHjUWubyz1QH>qgn*+FvKWL679j(0y;?|c(v&4Dz-fwb8t5TWhSSk< zOsqT;C$Fz5uMet72nYiJnbkzFHRQQ!@_ZF}3lzvVA#9Z(_79+%fe)l398gFXWu%9) z!WI?9cr|6v90_XIK$;1ZM1mdhV(FL~o4-IrC*gEYz?Rs-Ug%zRL zTI-;VX1WbN*%}vZr5 z5UV9gllXG?uadQTlef_%{!&8ee=)*;HexzI3Uw*<-nF?hZD0RTST`f8nH5!k`esBY zXB?^?I+jm69iDQ@9rxNZ6cBSWct>A^e^;zWN1}7%bGg!TGR}`IL$Il6&*D4;R|zmWLKr?=3%nvby&Fx}De8 z!1M|XHg+XI*DfOMyjvv#7U*2oasKxrW_z}`qG1hAU~H6(6JmF0U7z%+3MGC>fVoQR7#} zqKSeMxxMI;o&;Rz@LeO=P`$Eoh>Gm(QVnO8fLT7zf49v67qy-W0;KR20+^4H|tD;%qdC1x&RvRj1)F$%Q0Ny@SPUUsB z+T|VRntw{G%aWQ?IGGcs`4wzV@XdLRcEPyxhm=X;e(aWdRn>tz0p1JqjG6Q_#6{x4 zqH9s=3DP=Z1O~D0d^~vo^=&R|7+EF9ZwPot1Vhl;HWCeq70Xun;GRc~Rkv@AnaHKj zh9`zI(L0OKcmySv_HI2t9R3m)_FY-vqKL7$TkPYiezAb`*{yD|KvHz6kNh^^cw~*b zrn9Sb%At@U&Wc=42uKin0*f+emZKmUz|BEbA=0s02HF}d3f9PnwRcG5`IW`!+wVI8ks=xy6V2*a-qdoX&f1YXt z50k}JFJoymGVy(Sno~50st+EWfSUnLL?1)uU?Kvc&KFGLsu6LK2W6h_I8g^ZX??!5 zt^mEZ0NNE0c=DwJbjEofN&|>Jc~fh==|{Zvv;DX+TP(eHI9Nn^a$~&>6a47OL3+vC zsL7!KyGq#|N8PiFlD3z+HxmF?Y5T;C0|ojy2Mx3LakKZB?N74X7rQAdEGok_h8ACpaH-o~vL;Tw#J+8+(UQH4-@3Lu1v25OJ2?fHltN_#k^dGcK zaIb*%3hF5V`FewE0b#~AHeC|v&)g{&!1tf8ah^ZzGvZiLsrswKs7kZy8-(6arfA;+UgSv4e zrqEYv2!1t?Z5N@`5vQgIvvEK1qTwF|E!jJ-Axut>AQ80nx;PW9qhXZ|YbE+rNYEaK z>%^H}1e>uFVY1XbHJ#->5o^Bn)>2Q`qKvj0Z#PC9K;7FN4=UnrC>(jOS5eR2uB^%< zzfTH+#eep<4!F@0mmu@Qp)a`&sWEC%a5YOBJ#Ppj?skQ(S;QU*8{^7kw!Nutorm#% zI5PrjE{9=b2$8VdqoMm++;z8!B1qyQAJ@=#-gx`4fx6pm?3VQpl$M?rUSmyD^%vrM z^N2KukT0H##qz1fbFUBQ{M$};!5t({aP}R%u_ttYP8obl?64+9Rx~Bm7Mv<$D^G$Y zJ01@b{}qWn7n=~kEtkdc>7fT($LVMgHkYf|5h;Ze;!E)zQGe zWKl`wjy|&CxmckT=!wiI%ob|H?1R5{EulB?XM*=W+uI=6(Kr0>a_0ZcKn8RT6vin7 zgTI`tiaZ>JQbeh$pw&=VRYe?HK^ui2DS=?7ypaM}aLNBxx$J>*{aw5e_KFZu)*6j4 zS4Z*)NH!5^B&musq#zBc;8QTvQ!u21D1Z{(NR`IL=$T*{=2(48(5{4G+p2+&Yolfg z>Uyov_LhVImD1x$?buAc=1aZmOTFw%1yqcSK2)Gz=e#L3 zUX&9alw+GHMQ*fgH%6kHk&i3i%*BT8EF?HN;+&i{K=pu|8_vTOzZvAGoC!g0x*_hQ zaCb_CJAJ1+E7Hw8%Ec|xVP~Z6{z!}Ck^F0soWV$=@hGEvF-Eg-P#_%W*K<(Ho5BHE z@mG7fuQMQT*z=>@m2=$1mJQ94v?WPo3zWVAWY)G5p5UO42={Ao4p)+Fn^LTsQY|l~ znK!1HT}%ThOa9pmUTqflR5qtd%qq__JW^zE_>g|-QD#XcV*@062!Ldp3{HS%%Ns^# z``H%;xmU(`ts`K?Z{FKyb*t5O^d;yE zcgW^n;ueCg~ z7)|~g#!(E-VbZzW!?k7!?GfmowN9?jU6E~#R&TCXqKLM4V|k9Rz6`u%9cAJxH9nqE#-lVk58*Fjet|Lxu zI}Eq+em}%6>dsvnv_F!tC~WM}6@Gb3UXZ=S`WCl!v?9C&jgWGEoI2|nbViV@bi7`q zDCevNyY!1sl|a#(bfy3{>3lk=H|d>s@pm~}@RXt-rHUdZYVHL1fg%!6ri2_T(q>cm z{&4u#Tt{CSebN}Qh~!&BwC`1a><|p4H1e<^CiG#*4>yHD~pa1;x l7x?oR`12R|^B4H@7x?oR`12R|^B4H@7x?oR_~T#T{{Z<(o~i%< diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel/README.md b/examples/AnimatedGIFPanel/README.md similarity index 100% rename from examples/AnimatedGIFPanel/AnimatedGIFPanel/README.md rename to examples/AnimatedGIFPanel/README.md diff --git a/examples/AnimatedGIF/data/ezgif.com-pacmn.gif b/examples/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif similarity index 100% rename from examples/AnimatedGIF/data/ezgif.com-pacmn.gif rename to examples/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif diff --git a/examples/AnimatedGIF/data/loading.io-64x32px.gif b/examples/AnimatedGIFPanel/data/gifs/loading.io-64x32px.gif similarity index 100% rename from examples/AnimatedGIF/data/loading.io-64x32px.gif rename to examples/AnimatedGIFPanel/data/gifs/loading.io-64x32px.gif diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/matrix-spin.gif b/examples/AnimatedGIFPanel/data/gifs/matrix-spin.gif similarity index 100% rename from examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/matrix-spin.gif rename to examples/AnimatedGIFPanel/data/gifs/matrix-spin.gif diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/shock.gif b/examples/AnimatedGIFPanel/data/gifs/shock.gif similarity index 100% rename from examples/AnimatedGIFPanel/AnimatedGIFPanel/data/gifs/shock.gif rename to examples/AnimatedGIFPanel/data/gifs/shock.gif