From 4db9a74db1315a8c390d9565b311321ee7499383 Mon Sep 17 00:00:00 2001 From: Thorinair Date: Sun, 30 Aug 2020 20:23:13 +0200 Subject: [PATCH 1/5] Initial jpeg support --- Inkplate.cpp | 69 + Inkplate.h | 2 + TJpg_Decoder.cpp | 176 ++ TJpg_Decoder.h | 78 + TJpg_License.txt | 54 + .../11-Inkplate_SD_JPEG_pictures.ino | 64 + .../11-Inkplate_SD_JPEG_pictures/pyramid.jpg | Bin 0 -> 105552 bytes tjpgd.c | 1959 +++++++++++++++++ tjpgd.h | 91 + 9 files changed, 2493 insertions(+) create mode 100644 TJpg_Decoder.cpp create mode 100644 TJpg_Decoder.h create mode 100644 TJpg_License.txt create mode 100644 examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino create mode 100644 examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/pyramid.jpg create mode 100644 tjpgd.c create mode 100644 tjpgd.h diff --git a/Inkplate.cpp b/Inkplate.cpp index 74f36eb..6e78c91 100644 --- a/Inkplate.cpp +++ b/Inkplate.cpp @@ -4,6 +4,12 @@ #include "WiFi.h" #include "HTTPClient.h" #include "Inkplate.h" +#include "TJpg_Decoder.h" + +#define RED(a) ((((a) & 0xf800) >> 11) << 3) +#define GREEN(a) ((((a) & 0x07e0) >> 5) << 2) +#define BLUE(a) (((a) & 0x001f) << 3) + SPIClass spi2(HSPI); SdFat sd(&spi2); @@ -26,6 +32,23 @@ void ckvClock() usleep1(); } +bool jpegCallback(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* data, void* _display) { + Inkplate *display = static_cast(_display); + + int i, j; + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + uint16_t rgb = data[j*w + i]; + uint8_t px = (RED(rgb) * 2126 / 10000) + (GREEN(rgb) * 7152 / 10000) + (BLUE(rgb) * 722 / 10000); + display->drawPixel(i + x, j + y, px >> 5); + } + } + + return 1; +} + //--------------------------USER FUNCTIONS-------------------------------------------- Inkplate::Inkplate(uint8_t _mode) : Adafruit_GFX(E_INK_WIDTH, E_INK_HEIGHT) { @@ -513,6 +536,52 @@ int Inkplate::drawBitmapFromWeb(char *url, int x, int y, bool dither, bool inver return ret; } +int Inkplate::drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool dither, bool invert) +{ + uint16_t w = 0, h = 0; + + TJpgDec.setJpgScale(1); + TJpgDec.setCallback(jpegCallback); + + uint32_t pnt = 0; + uint32_t total = p->fileSize(); + uint8_t *buf = (uint8_t *)ps_malloc(total); + if (buf == NULL) + return 0; + + while (pnt < total) { + uint32_t toread = p->available(); + if (toread > 0) { + int read = p->read(buf + pnt, toread); + if (read > 0) + pnt += read; + } + } + + //TJpgDec.getJpgSize(&w, &h, buf, total); + //Serial.print("Width = "); Serial.print(w); Serial.print(", height = "); Serial.println(h); + selectDisplayMode(INKPLATE_3BIT); + + TJpgDec.drawJpg(x, y, buf, total, display); + + free(buf); +} + +int Inkplate::drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool dither, bool invert) +{ + if (sdCardOk == 0) + return 0; + SdFile dat; + if (dat.open(fileName, O_RDONLY)) + { + return drawJpegFromSD(display, &dat, x, y, dither, invert); + } + else + { + return 0; + } +} + void Inkplate::drawElipse(int rx, int ry, int xc, int yc, int c) diff --git a/Inkplate.h b/Inkplate.h index 7b6d7ec..9d0186c 100644 --- a/Inkplate.h +++ b/Inkplate.h @@ -237,6 +237,8 @@ public: int drawBitmapFromSD(char *fileName, int x, int y, bool dither = false, bool invert = false); int drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool dither = false, bool invert = false); int drawBitmapFromWeb(char *url, int x, int y, bool dither = false, bool invert = false); + int drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool dither = false, bool invert = false); + int drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool dither = false, bool invert = false); void drawElipse(int rx, int ry, int xc, int yc, int c); void fillElipse(int rx, int ry, int xc, int yc, int c); void drawPolygon(int *x, int *y, int n, int color); diff --git a/TJpg_Decoder.cpp b/TJpg_Decoder.cpp new file mode 100644 index 0000000..fe76eb5 --- /dev/null +++ b/TJpg_Decoder.cpp @@ -0,0 +1,176 @@ +/* +TJpg_Decoder.cpp + +Created by Bodmer 18/10/19 + +Latest version here: +https://github.com/Bodmer/TJpg_Decoder +*/ + +#include "TJpg_Decoder.h" + +// Create a class instance to be used by the sketch (defined as extern in header) +TJpg_Decoder TJpgDec; + +/*************************************************************************************** +** Function name: TJpg_Decoder +** Description: Constructor +***************************************************************************************/ +TJpg_Decoder::TJpg_Decoder(){ + // Setup a pointer to this class for static functions + thisPtr = this; +} + +/*************************************************************************************** +** Function name: ~TJpg_Decoder +** Description: Destructor +***************************************************************************************/ +TJpg_Decoder::~TJpg_Decoder(){ + // Bye +} + +/*************************************************************************************** +** Function name: setJpgScale +** Description: Set the reduction scale factor (1, 2, 4 or 8) +***************************************************************************************/ +void TJpg_Decoder::setSwapBytes(bool swapBytes){ + _swap = swapBytes; +} + +/*************************************************************************************** +** Function name: setJpgScale +** Description: Set the reduction scale factor (1, 2, 4 or 8) +***************************************************************************************/ +void TJpg_Decoder::setJpgScale(uint8_t scaleFactor) +{ + switch (scaleFactor) + { + case 1: + jpgScale = 0; + break; + case 2: + jpgScale = 1; + break; + case 4: + jpgScale = 2; + break; + case 8: + jpgScale = 3; + break; + default: + jpgScale = 0; + } +} + +/*************************************************************************************** +** Function name: setCallback +** Description: Set the sketch callback function to render decoded blocks +***************************************************************************************/ +void TJpg_Decoder::setCallback(SketchCallback sketchCallback) +{ + tft_output = sketchCallback; +} + +/*************************************************************************************** +** Function name: jd_input (declared static) +** Description: Called by tjpgd.c to get more data +***************************************************************************************/ +uint16_t TJpg_Decoder::jd_input(JDEC* jdec, uint8_t* buf, uint16_t len) +{ + TJpg_Decoder *thisPtr = TJpgDec.thisPtr; + jdec = jdec; // Supress warning + + // Handle an array input + if (thisPtr->jpg_source == TJPG_ARRAY) { + // Avoid running off end of array + if (thisPtr->array_index + len > thisPtr->array_size) { + len = thisPtr->array_size - thisPtr->array_index; + } + + // If buf is valid then copy len bytes to buffer + if (buf) memcpy_P(buf, (const uint8_t *)(thisPtr->array_data + thisPtr->array_index), len); + + // Move pointer + thisPtr->array_index += len; + } + + return len; +} + +/*************************************************************************************** +** Function name: jd_output (declared static) +** Description: Called by tjpgd.c with an image block for rendering +***************************************************************************************/ +// Pass image block back to the sketch for rendering, may be a complete or partial MCU +uint16_t TJpg_Decoder::jd_output(JDEC* jdec, void* bitmap, JRECT* jrect) +{ + // This is a static function so create a pointer to access other members of the class + TJpg_Decoder *thisPtr = TJpgDec.thisPtr; + + jdec = jdec; // Supress warning as ID is not used + + // Retrieve rendering parameters and add any offset + int16_t x = jrect->left + thisPtr->jpeg_x; + int16_t y = jrect->top + thisPtr->jpeg_y; + uint16_t w = jrect->right + 1 - jrect->left; + uint16_t h = jrect->bottom + 1 - jrect->top; + + // Pass the image block and rendering parameters in a callback to the sketch + return thisPtr->tft_output(x, y, w, h, (uint16_t*)bitmap, (void*)jdec->_display); +} + +/*************************************************************************************** +** Function name: drawJpg +** Description: Draw a jpg saved in a FLASH memory array +***************************************************************************************/ +JRESULT TJpg_Decoder::drawJpg(int32_t x, int32_t y, const uint8_t jpeg_data[], uint32_t data_size, void* display) { + JDEC jdec; + JRESULT jresult = JDR_OK; + + jpg_source = TJPG_ARRAY; + array_index = 0; + array_data = jpeg_data; + array_size = data_size; + + jpeg_x = x; + jpeg_y = y; + + jdec.swap = _swap; + + // Analyse input data + jresult = jd_prepare(&jdec, jd_input, workspace, TJPGD_WORKSPACE_SIZE, 0); + + // Extract image and render + if (jresult == JDR_OK) { + jresult = jd_decomp(&jdec, jd_output, jpgScale, display); + } + + return jresult; +} + +/*************************************************************************************** +** Function name: getJpgSize +** Description: Get width and height of a jpg saved in a FLASH memory array +***************************************************************************************/ +JRESULT TJpg_Decoder::getJpgSize(uint16_t *w, uint16_t *h, const uint8_t jpeg_data[], uint32_t data_size) { + JDEC jdec; + JRESULT jresult = JDR_OK; + + *w = 0; + *h = 0; + + jpg_source = TJPG_ARRAY; + array_index = 0; + array_data = jpeg_data; + array_size = data_size; + + // Analyse input data + jresult = jd_prepare(&jdec, jd_input, workspace, TJPGD_WORKSPACE_SIZE, 0); + + if (jresult == JDR_OK) { + *w = jdec.width; + *h = jdec.height; + } + + return jresult; +} diff --git a/TJpg_Decoder.h b/TJpg_Decoder.h new file mode 100644 index 0000000..69fbf7d --- /dev/null +++ b/TJpg_Decoder.h @@ -0,0 +1,78 @@ +/* +TJpg_Decoder.h + +JPEG Decoder for Arduino using TJpgDec: +http://elm-chan.org/fsw/tjpgd/00index.html + +Incorporated into an Arduino library by Bodmer 18/10/19 + +Latest version here: +https://github.com/Bodmer/TJpg_Decoder +*/ + +#ifndef TJpg_Decoder_H + #define TJpg_Decoder_H + + #include "Arduino.h" + #include "tjpgd.h" + + #if defined (ESP8266) || defined (ESP32) + #include + #endif + + #define TJPGD_WORKSPACE_SIZE 3100 + +enum { + TJPG_ARRAY = 0, + TJPG_FS_FILE, + TJPG_SD_FILE +}; + +//------------------------------------------------------------------------------ + +typedef bool (*SketchCallback)(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *data, void* display); + +class TJpg_Decoder { + +private: + +public: + + TJpg_Decoder(); + ~TJpg_Decoder(); + + static uint16_t jd_output(JDEC* jdec, void* bitmap, JRECT* jrect); + static uint16_t jd_input(JDEC* jdec, uint8_t* buf, uint16_t len); + + void setJpgScale(uint8_t scale); + void setCallback(SketchCallback sketchCallback); + + JRESULT drawJpg(int32_t x, int32_t y, const uint8_t array[], uint32_t array_size, void* display); + JRESULT getJpgSize(uint16_t *w, uint16_t *h, const uint8_t array[], uint32_t array_size); + + void setSwapBytes(bool swap); + + bool _swap = false; + + const uint8_t* array_data = nullptr; + uint32_t array_index = 0; + uint32_t array_size = 0; + + // Must align workspace to a 32 bit boundary + uint8_t workspace[TJPGD_WORKSPACE_SIZE] __attribute__((aligned(4))); + + uint8_t jpg_source = 0; + + int16_t jpeg_x = 0; + int16_t jpeg_y = 0; + + uint8_t jpgScale = 0; + + SketchCallback tft_output = nullptr; + + TJpg_Decoder *thisPtr = nullptr; +}; + +extern TJpg_Decoder TJpgDec; + +#endif // TJpg_Decoder_H diff --git a/TJpg_License.txt b/TJpg_License.txt new file mode 100644 index 0000000..590dbde --- /dev/null +++ b/TJpg_License.txt @@ -0,0 +1,54 @@ +This library incorporate the Tiny JPEG Decompressor code files: +"tjpgd.h" and "tjpgd.c". The licence for these files is: + +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor R0.01c (C)ChaN, 2019 +/-----------------------------------------------------------------------------/ +/ The TJpgDec is a generic JPEG decompressor module for tiny embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2019, ChaN, all right reserved. +/ +/ * The TJpgDec module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/ +/-----------------------------------------------------------------------------/ + +This Arduino library "TJpd_Decoder" has been created by Bodmer, for all the +additional code the FreeBSD licence applies and is compatible with the GNU GPL: + +vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvStartvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +Software License Agreement (FreeBSD License) + +Copyright (c) 2019 Bodmer (https://github.com/Bodmer) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^End^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino new file mode 100644 index 0000000..671ecde --- /dev/null +++ b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino @@ -0,0 +1,64 @@ +/* + 5_Inkplate_SD_BMP example for e-radionica Inkplate6 + For this example you will need a micro USB cable, Inkplate6 and a SD card loaded with + image1.bmp and image2.bmp file that can be found inside folder of this example. + Select "Inkplate 6(ESP32)" from Tools -> Board menu. + Don't have "Inkplate 6(ESP32)" option? Follow our tutorial and add it: + https://e-radionica.com/en/blog/add-inkplate-6-to-arduino-ide/ + + To work with SD card on Inkplate, you will need to add one extra library. + Download and install it from here: https://github.com/e-radionicacom/Inkplate-6-SDFat-Arduino-Library + + You can open .bmp files that have color depth of 1 bit (monochrome bitmap), 4 bit, 8 bit and + 24 bit AND have resoluton smaller than 800x600 or otherwise it won't fit on screen. + Format your SD card in standard FAT fileformat. + + This example will show you how you can read .bmp files (pictures) from SD card and + display that image on e-paper display. + + Want to learn more about Inkplate? Visit www.inkplate.io + Looking to get support? Write on our forums: http://forum.e-radionica.com/en/ + 15 July 2020 by e-radionica.com +*/ + +#include "Inkplate.h" //Include Inkplate library to the sketch +#include "SdFat.h" //Include library for SD card +Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome) +SdFile file; //Create SdFile object used for accessing files on SD card + +void setup() { + Serial.begin(115200); + + display.begin(); //Init Inkplate library (you should call this function ONLY ONCE) + display.clearDisplay(); //Clear frame buffer of display + display.display(); //Put clear image on display + + //Init SD card. Display if SD card is init propery or not. + if (display.sdCardInit()) { + display.println("SD Card OK! Reading image..."); + display.partialUpdate(); + + //If card is properly init, try to load image and display it on e-paper at position X=0, Y=0 + //NOTE: Both drawBitmapFromSD methods allow for an optional fourth "invert" parameter. Setting this parameter to true + //will flip all colors on the image, making black white and white black. This may be necessary when exporting bitmaps from + //certain softwares. + if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, 0)) { + //If is something failed (wrong filename or wrong bitmap format), write error message on the screen. + //REMEMBER! You can only use Windows Bitmap file with color depth of 1, 4, 8 or 24 bits with no compression! + //You can turn of dithering for somewhat faster image load by changing the last 1 to 0, or removing the 1 argument completely + display.println("Image open error"); + display.display(); + } + display.display(); + } + else { + //If SD card init not success, display error on screen and stop the program (using infinite loop) + display.println("SD Card error!"); + display.partialUpdate(); + while (true); + } +} + +void loop() { + //Nothing... +} diff --git a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/pyramid.jpg b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/pyramid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bad7fd0e1ec15fba615ea201efb7188aff210076 GIT binary patch literal 105552 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%0AOW zF7g}q)c3jD2s~n^mRGsmN_1mJ%W`YBi~tJm^(LEURFh{nt7y>OE6AWPrYn}N9y3=x ztTsAynu|ESOG%an$Q)OXTMFXOrJ;?C(VnptCnJ$qC|K%qxnkLJO=|>YQHw4^Vu=bn z4Dw8@6-PBL7b~4dR%qGUEI?!e)xA1OCmmYOMb&IJ(hYC1&eoSJ>cAije+tf`wx+mk zbSUf(4p?=poNkV;YfD0e*7zr9_rb~@o!`;emi+}2Tsx|%j#v?Dm_T{O;Gq;~*s z{l`n1d=x{*#4fvy^2&}Nm?h!;<1(SQoUWeE`-4j%2!7KF53qmRoM%{(->gdi##`$zKf&PP;SG%Ja(kVszT(hvn#a=BuOL zXxf9>Ut7E~p~oYJ8R`#l?_N~sdns9?^b9sSmM)U1N>@+t^&ZiMh%Xu(~Z8hwONXL9a;Q(d%h>Ia=)6DjSn zsbUqb6;$*!*-a-a95tI!yEd*upu=O4j8|Nd=F_~kIqg?XNeOsY4b+O^t%pxkdiZQp zj*>a~Es8k~F!s!>R^vUNV2v5*gO)Mjk%U2f&ZqBzz@W_QyTeZjgHS5vAsOux=@ma4u<4oz% ztS#eMlVo0;U{`YGin2ULt5kf?Q%^>VeBq7=#~$@*T;at_mNqsTTQJ{bx>Ru>|n=Q{dlBHGZW5*M%w#FSjX(%+T zdNP`Gx-hToluBJIj;#c>N2@}+wV9i79!}BLxn~=z9gvl^E@_$+_ar+4tM6J-!8z*A zd`4jkdLyf_@cpf_?vSY~MPbh9>)CDy{~4*A+}BGd`;Yn?u>%v9>-Rir9vJO;tQ3 zU^>~9s%VbGL{^U9U>`3^>WZ>HdluBCaGyber~6cDH)k~cn!UmqjMRAG{9U75nNw4SHI=6OW$?&)Ys^TzOoRs>_Udz|U551&-p zm6@A7%PZ_0A8N)<=WHP0uFM^DJ+lWKV!5jOx}L5M)ih?Wh4mD=3XI<{t+4eJjts{N z>D{Dm>erCkTA3L171KE=tDasOgsRTS^Q~82l6#_JCvI!bsfcapeTEw@j3Mt6=&IKa zWRbI;)uk29sh%yWYRI#66=cEYu1Lb2+9%U=Q*$tuMcWrsQmCfx%&{2nXC76#(c;eC zMYIaBx36gHi6p%$>RxZbG3Zz`QSxdv3xp|ri+vXp4nu;|w zvpO;OD@U3KZ=;oHx13|^UGUiXoKk95GkjC7TG`vW?o%gWQC6yoJ2LFPrD>yw@kTbI|%Vm(!u|8?Zwlvjv;$T``V_l{v-K(5);HT&atL>+Mr0Mry{Lg3OS&vFvl_zZq(nL3LShyQTdJ|bj?J~rO&m$ZvsL3bLRYETIMs;M8yV&lu zF9pD_9p~mvn$k%DU)oXOnvFm$QZhUEHYp>L@bfPle9=Tp}M(yA3X zos97~S>e|@I!zhk5`$|VSEdz7{RcJE7P=lLDl%*LjO+%=);usxS2 zoN~nVrx#{?4Bt8k3f zrnEeYSgN>5so|vm0MFF&j~RGseF|XjARotCX9g~1GP-K%)D)WWSft&2K!x0iSvbCbxe zDZ~CG$iBu9YK(5CW&nBH$7h@v!NW%c9C(^K+Nc1U2O=@Rq7RxBypa!mb z)YP^+C{=OMn8pW=PDOF@w9iRYQd$hX0FDh!O;J>xit$>!%mRXIA7#wVrCC8;oj!x% ziKfJ}Ayw_~UXC9G-m*MQZfzKQv1d(X6}^^YNWnh6>(GQF4H4p1sXE%WHceI^Kgyfs z_owX@?8h7&xp|^Vdut8!N{~1{y!5JZQd$#6w5UnBBWuEc=%2ZA&H&)m!`;yHGe6$@ z$km$4Keik8BWXP6wNDbqg9+0)s(_R5ktDUB?k09uAO)_&2nu!Nrfa5PQ3b6Q^Q3& zvpV?6HE5fV$!wrPHjhf{N-IQpa*aptG1vBv_Mky0%z9#pLhC_Ltd#xaTky0qMB8U? zo8FvLTb%if<0W{TpJkD*{N4WmFQrwrJil!fPnN}rzRcsvmj%01x-y5fkG&I_)-0}~ zx&{T^9VVYc+rrnUID6Zg+GIpp^ur$1?0NW!`{eMm!=Ev^^r>>?S7vgGsiaY7Nz?DO z8~xM7QrG*q>t7|CXMCx`n?Bbi;ima&)<=75rRnzZe&z*IM>wyGsakV)Z2S6DqfJ>M z+h>4-3o`u;W6X4Nr@0iD6R7!^lb-Z*GPw~NMixPul#98)4b{9>=tCB8KIrXTN@*jG zP;HQ3>UVcAn91_v0~lITs@UU2C#jHl$lOP*McJ#0Fm>H2FC$=M%Y7?aRpm}sOwOhr z6=P{5g48W#(r!vhghbxmE9|n2(M@tqp9`L3(u4Mt)`KB}DI83PK9%&5w6#aePNbr_ zS)bxdC%RQo52Zy~TfoIxnqLidwUZc*!{OK(hDoq=GZ@uWE-;T)|^*1b3_4~oT9Zlh@&my0|*aXrIbLhR~4O69FV%T#>^S((BRROGD8OAR(d zaDj5ATbjz9CfVuM#YIKj?5?9LVi8-MU{`ZmSoz9wy<4*{Pqk}yVc zTEk_PM20eQT9auz4lr$8&eU|s&I%8l^{#5TboEDN3|y9s$CBK{_%+1f;`Kck)lxwM zx0&riK{l*c)KT2we;SI3vsw~MRzFBd))7x7JD^x;9$}4yx`$2g#~zp&qHmL~dFswC!tUnI0A+RFPR@pR+K8m)0WKizY0n(V~+kf z&*S&26W*TfOINFD_h> z+@EhuVxEav?0L;jH96_FrPHD3emC$$ns}1$N6pQ=XZNuy2Wb6pN8TR)0RFn-t%kDZ zr?K^UUU;g@*y41TN74TP!8vH)Ce?!i4muH7DCm1`r@oGAS!9aiaNwz~NmOZ`)L^9> zGpX>^-`d?-l!opr(8E;cS3DfTkFrf8Tg10>THAzyPUak+O5VKOR)+>(LNzCOoUMbi z+?A6g`qvdXHuXK)(NTrbr+9V%0M2WsZpRfbc%gWKfWYRksIGNVm4&m?XT6Xr4o|I2 zF!GzwD&rwVU7a?Wp+RLInIRHO{Hwl}6+>6K;$kxi)^=JV{I_z!0dSdB?OV5Gaz>15 zO2X+9L#dtNO0et2C|Y`kDl)@pa{e+wZGKTRp0&w|_gU^`QudUt%Dx}gTGHsGq_A#2 zl@x2)*_{~N3abmm^luAHmks7GLtca*G4Z*K9W`cB@#em5llwl{5#C4sm7Q9a(H&VX zTB<*7Kap3#aV^}dGdXkWO*&CeQ*7F9lzCFI*FExDT_wNXbJPyiwd~KISa((qeF$}J zG8?sN;f+L{N{;mTQ#8Y3vdlQ)MWb4qHnmCnvG2^o9#_&gVqqI}VBvYLFS7p;j%VLzEtix@1T}N|2P>4gAx|QJ8a#l5r zxwSJ1RykT%ESfa85go%VMa1r4YdTDNh;Zn zHvr`C9nQijZEo#%0v$)acg9hYK6X&4N_HWZ?%EI+U{yS46s1ukNYSZ1Zfa?vsF#Uf;~N`=U1_C!Og?kG7Gy1C1CNWQ`d^CBU)IA#acxh z*`bCO4Il+ab5i7%=S>(+a?u=audK-}#@G(kUjDVgR=aOQ=`gq$!Z(pFhRt(xz27W5 zQ=<8vrg+4u!pR!?gQNsAoC@fh<=pe=MbAW5mKf%T%phgDR#JkJxwL9Z66G^zw`f}+ zaa^*d;&#HSnj-SSIU=*=xu-i@6T%fp3I$wJMl)^d%9@xSXSORE%TuEc^e8k4z`i{+(cO$J~RY@MM2~(+}*oR2g z)_D@n;xU;UY2DD$r&(;xF*$pRbs+XSYea#r0JV>9-Y}z|$X8TamdA%x%}4i)&hZtb zv)+^`xRJtuK_k@Xnx_lycHpZ<5V~bOE5gu5&n4prVe=$Q(0`3(96Vz6H^k=il1iSQ zXF;THw4V_Cih3|457M<-9!@E$vAmABC6mIiUp8cy1euiJf;a(jkPdT`l6b%as#9s- zq31eww0KqSK ze%dQ(7t4Vq=jDx=!9LhhGgUN_IdK)GIVS%A;2RhAu+OJUcX%T6AQs z?)J&7Avk=yyNG5J+DWC2OlT@OZ{QB&Gpdn0GUmzNh7q9@u5 zo)J$!j~}Tx6>*bF?BK^?pr+JH{wMIAwxovRPBFuBcy$@t=N)~C=9NlHdv0lf&8lIV zjcsGox;(b)!_z}>-)x1WV0jJ3ex9}Gye7Ai_SEwz&Of_%FKIBWR|+FgG3i}2si?gU zTunzP>Tg~_9I)(^1I2X8DXmW`jY&pViO<~Fc@l0+V!5SDmFRlZ@f_;RW1~vpRd*Uq zHd>O3yDwSj%H&Bibp9IJ7<=l@TDb0AB3=lvFc2;Xu7s3!IeDDdyj9gNC$?bBI6Zi& zoSaSY^dV7M7A!Rzd-%nv;GVSOILkvS_*u|==+o0J*52n3r~viitwkrH!-%TtL*6mG zQK!A_=@FnWoP8?_)ZF$xEV~k@)79u#)BGf{shZ%2an8|M!wQzH+P-q+`C+ltcwCEl zOeq-JJ*%QR9v))7q%JO`T2D0yS-X$#Q*_kMd=_gg8|8ZxygjG3mvUm*df?Mg zQP}CnVpb*`*Ea6f1{8yOHs3#~s8thoM^nqC8g5h2 zyCsTUOZJ5TNP2Z%& zQR!Te!RvN=HFIck+9ze?CsyP&yrK64*0Y_3b<4FNS=Oc?GQky9#aMtB^_ z;eFCy4%r(h%tRhL(~LPt$Jh4Hn7ex=y^$MtGr#bUTF$j-QCE?M9*tZ}<%%~vF`|98 zL4A6sAbg5P0=^?R&E03o2ch~#MUcl09(s#Yy0NrKVi>>++t$8bS8nY3)RI<3lt(8h z2fa;(SGjjgF-I*_a713wf0X3AGk-TE!CAOawsLWq!Jq0Bjl;>ic z@%eHT&TwjMQgL+f_*8H*_U;suBgsuAQC+)>CMI65mG8j%=5i(N1sT$kS6IN zEzcc2>+JHZ-87*y;d7jo#rsM~g2FhR#EF1=*VWRJmZ(QC($028dq15hjFS&YjKG|Dr=e4aeAJlFp;Ydbkw%;E<)U>$vrDY zXQ{(eo}J*xJVgRcZUZICT+78Fl(&nhHUeI;XW_*@8 zM*S+OVmV!z%Z&sZu`7%ht8~TNn$hyWET_ipH)YFh_n9EvZ^o zYRKR&wHuqM-bax9=OhDObE@Retfz*jN+~6-X^eaf8Il!zOO`|Oa!kCb;%_OxPrKN}e0A^k4b9r{Kv*YhKW%`;@r7Die6ZNWi zc=Juz>hzxt>H2-VM)gv8PdnT0gXvvV@X&(ywLBb7Yg)YDveAAfocoJ_SfC^2Wf@aj zG;v1OzUEDn-G6GEX_UNYJ6qDTN%L8rg=o|EJn_+%{80?>>1IJ}7q;@4E$j_dShXDt zGU>)uY}w~S%{eRzkaP5}E#BwX`>SJXMDaA1&Jyb`2MZwTD{58wUZdHi~sr{rgV zpsTj#?2&)M_YzBQ<=nJyxk+J?NF7dlDD^d5?kMHN(1V-h(G~T(-9JJ^xeit-T&B?F zM*!sGt~lveI+0Pk3??4FE^Z4}y4dso02lbCeP(a5**nK#qZsMG{{XFARVs78cVp`^ z+^u0F&sSM~Mn;VwT|r@V3{OGVG^xLO$9^t1q~w|EI!w_=r%AD7Vv+7;4UnVU0iVqJ zS7p*Z9}!M+sjuEa;r(jv^7y=@!wR34J;5?RFaEtub-m7vUKSK$l6QUgHFcdD&%_ou z^9f`p=Z${u27C46C)f^ZoRsuu9vZz|1i9g=eGU>|4eA<2;WCdp+@F?J9DY8P3X_9A ziaDhmT|ak8b{(nP$n31Gg&jp)(KvdsNvF9&bZHJd3WlzA!b&=m+9kXgY-IP%Tq2Q; zNltnb#|z5LV_*kb(IY8MMQT#8`$^P{bEodf`C6tozNb}hXY9G7xOn2()&w!H)}s)g zE`gL)PEO|-lg()=%8XTodHn7U=h8ZHs~%Ue(rF$m@`{<)Ez>pC3|i`V*sPzL{D(;- z!em*Z##@@|z0Vaxk@CpS)$gw@Hadd9_NbJaJ8-yqRc@Kk_*O-^kIZx`#C|n#cRYN` zE}ODtueMzcRK*(YiFgF}rFU}=VNcj|JT8arZfz0uxn@}!F7-Dh@E`A{3{->ZRYO*1ococn**(p|U!*av z7r8ancY7Z>8c=kcp5?oXXs(ki0GF}#tqyoql_#dhHLm&AAfGRA>BT8K9^Ma@w^Dx( z6`e$zNF;Q}N=VBboF6HmlgKYTwL%bapITScBgY5@w}Y6v8mx#zWhdzt35#LcL^57jbUnw}1t=3epCJ+-uG1S^g! z!#;%!-6l4!4Nc3iuRMzwLBYirA1N9A`1zO?H7`)Li2<74J&L@0iZ5g>$Yhnt+E5(Q zxY8xLfWku(RCmo?tR(Jd+&f4LgYwkg=7|wS8BRdS&q`MtG-A5h4lqgj)$>@%#HeQ7 zCU8w8$7>?w1J7S9u~D8)TGWT2lG__*E5A83?P70pH&L~P{**!wv}dudjtwRyRk(N{gW@ zNffQ)m9E)kA1`A`UizIAlvFPhAI0z#QRS&ESYi{lhohC$yVQrl_g76Qx0Ojz)7qmA zDLomcX-1llJWa0^%Pe*$%0u<8itVWAdAV#ODxUK^!sVuUnWfI`_pb^~x2g8@)FT^2 zp3X8sBvXpHm04KV&~<<8>z^rsWOIt>pEUa8MPtCs zaL$&b-Xgv>aWRcaC!rO1ikyn(cpN{rmZu~pnrTduXP&j@^SL{-=s6<=%8sW?g$0mq znH*PrG`UX;6E^Btt-m)OQmOYfmG!Z=Eu+lMJvT?1<(7FvM`{=beuVo9xYFj;>~UAd z!kXodvg~$o3plL)%Vemi0XSa%`Sq^9cGijWb9H4?o~e_mLps{Kw4|WuGyN+m$3xJ= z&JJlMsmhXG=O3EURDPxIVDF9|FGE|>h>c3;c zlpLFkothu7;Mgp?l&Ro+xX0_0RM@ae)h*QXy=La#+V9H>3o`uRcCR+PqZw#@eioGK zLgp}&e7tlfxvqLVyr|dFb^Dzu)-p_%x#d5*?kh@lrBBe{#bxyIO8ZN>+W?zDWWU

BauZa8cYp|3`rNYq*%Ggl8+2`1Xq+tK2;g5(v-E8>o{zKxeqju_C5@WyvEjypLgp83h^njs~#I43HUpFA{w)`u&1;(a4Zj_%rd z8H^;Vz$M9I4;*?_&a77Fr$Z~m;v8oczgr%6t!p>>-P>MYKI4*66k!k^z*i%imgm>u z@RYEzsv^qSDA`CVWX6C2eGcW#w%8Mwd&N8 zv~^2#dNzeKHL*!cPM~gZA8-Tv*v1E5t(<3oO;twEnVeFSTPNgD*Y7NJYvgG^bSKVK zwlGJq{#4ydE3)voYPguV$6iw|9ezziR8_PAEz^KV#_z+WB_`jI*F3#YqTNr4oy1#i zkxY-~`Ko#i**N>W01skuz!kJ*Z4aKQO{sGunb-6QP#LU=l{=^L5Amlcc0D{6V;Ot3 zG=0uKJ3giPJ4>%O+AmDnw2}ICyC=kwcjHRp?RsRWgSPC$;x&|ncrP3 zvoatA1J=Asl~Z;{+fkz78?v;d#xq&BV`#2pV?*%-@G`>8Sw~FQO&n8R>pc8sTM7Hf z?6kZ1bww=bxlS=&l{$)N&EjzL!#gLryP&HqjpdT7=~j7I*(SZ>*N89ebgNakoR2V` z26-Z;r9}mHV}Zle#ZAIH9&h5`6Hlnj6mCct?yhUktB7)E+2xs3F!8?Zi$j_l$wW9D z4u-0Pl--NQLR42V+4z3kLvp@J+3Q`(HhLd7j;QAkdsCp)Cx+J5t|uTA>7Mm%q^@}s zDax$*BhBV^tN3@Z1Mz)hsx$?$T1Ifiz zIJvtbDoH_H+|;aW?JbHf43(_uD5DB>oY{j6aTh*m!qy2LO^PCoWIkEQ?0qR%FL1kA zjv3eCP?ET?a%VcKdc2pLtI}pr63Z7e)dl_1U zZj#{+;161tp>foya|Aaw<~daIDW>D96N-8>g7F83t}Lf}s|CbomKF3_W^F1~jL(|Q z@>thg@Z8L?xQ=s-;n#*9mG;ytLaMqyKE&bYLAc2Ye`g)Tk1+hTtfHRe(XQ#Qbc)j6 z8+pHQfFD|vlGxU*Nz{+L$+bwHXh31drBv1TGQ&+mS{#O@rok%`fMj}C6fFreG>;5N6APQ~nZd|UO6sSCjCGOZ;w}C|-p8Ef`pcwi=+-b<03uF% zlU-D3#-7KYi^NX0?3VTwxs-_%5$RPpscvUVqo%YdK9L%>SEr?7@aXGU*)^%51V_p8 z)pAb&0a|j|Smd1j>$68o(&M(cOPlD%F~X7G-h~wxc5&iylBov@Ow*S73AbB@Qm3%v z6x~R@4k+R04}8be?XF>&B(s^fsoXgJb(@3T9*!QZImN1rL!^ML_B-Fo53?9hG-5KLBl<^U2T0ZBj_*U^4BU>ehVrIh(IFZkf_k4|CHjuLZ7~BHKdmBBtO3nFAc; z0pIIgFI^9glpLzwPk$rIHA~G#{`OnDwr?;K^0!18>0GawyR+_a^k`A0sngr@J6{0Z zTV6n-YerdN_v%?kf0?L({dC z`$T(SxnG!I;Qa{xwDgwdxIBGa8|B_C=I?Dt)1-;l1Og3PUg_%6t$M0R^Q-M1<>zIE z3vM;#&V>22KEjSRnvLYnm%^4876lS{4&O}luFO3dQ(B%bWr&4)xFb`?Htl(*h~jbx z8NlyVil*lqBa0`hsMWlY<_l#cHv~B8UJ|6^El;;~<2dLhqNE`8qOQZB^(jWO78?Q0 zbFOCeI%LvXoeqZA@W`zW?Ee52dbo8x4GSp9z?y8Ch&` zUN(*`IvRpV*asYbv=(#ni@cFDEAApO;@ahxo5N4#(0nnXCX;BZBkrAsWN%vdJZ?I8sq;wvI{}x{ z!O~EIG=9k}lm6=rPat|%n&WnQe90|Iqj$Gc9MNS@9Vw(IuEyq{duwfHy4^(5uHPCn6 zw;+$!hAvVWqjy$thhX%nWhY{t!nlyJG%=y^+&T;_VcXt|g^E{;IFuZfJ!xuENQx;Z zGJv^bp0xD~BWF%!6EE6aDHWuA%%-%^xvFX@7Sy+FrCg+ulpx;a2rtohF#xfsQ#5qQ z9!=Yu0|eB)M{5<@-JQNp{e0qRVQXkvpG)}_;$@A7g~ClrCgTBLtfrp znNEz9UdMx)Wl+S$$!>CX^QFod%Mj_o2d#b03`A?f>7SiqF!RGs(n4wQ%9o_SD0)`# zi%gpHjH&6ldtQcDwDRHLo~E^&ZjM|O;-`5SHkxaF$r$t0n$M8+YT}mY(zmm-wIsv? zN$N!kDQt1nuU4JDPT)(#96(W$wgsDtVGPsydaVyAhd|>}NZNsHo=VE0NMU zUl6O>UU`tSAyb~U#a2A7==bm~RfVOgf5W=sT&u%(B8m74al;SstD|1}GnXBgI(C&k zj*{BhUK5$ZD{w(l$MCMSj}moSmc=V=D$DH-*D9MptDL5_rrJ)s zXH@e?an)-Yv?DdC?qM-iAnzBU&MmV;DN%qk*VerLPFG{ubyU^L-`Tesh5Ew;W!a86 z?OFRNRF#pp)}ca^l%0;RMeuxfp{KjUnEI;okF9pn!KiB^!Nuopt)ov%oo|O$C|oQc z5Dy@#k^1zmxY=xc{x!;f5z$@gmb&yUX?>ZdWXgcKE9k)V01o_Tpcv+--D=Ncnw?6x zX)2VS--+g${{Vq5G}ik}0cDJ_$zI=wYRY&-oze8zE+K{?a!W>6wSnWcb$kK_4r^G! zq-#>5l+#2yUx(tfc{#nBkP&=r%wL>WD$ZHatry6F^_PgKE2882YSwY*Sb7+7;AJx;tva0YRTpp z#1~382EfYf|o7t7t{QVJ3S`rO}P>IILYH5(9`8;?yHQah=uNT zHnKCsVVI4fKJOS&+?;(+8Sj#7HSW8fWlKqDaT=eCAhf=^w6KjQ3fp6K9B1oUO0vC? z?@-C7R-9^8cJ5yAubV2h?c9?~EQjubFiG{TDW_|*moemv_HaogbQiWTUq)2nWFt8T zIr{$qjc*ro$fsU-d3Q%IePs3pl1NAczB<*T+oN4q)iQ;IvE5t{hdWL&Le-shp(;_k zEclOYyb4cKz!ayVFu-1GByi(rYZ~_!`hDuHwa`M~aC?fTqV#8<3bp9Icx-YWAn?;gIBXV(^dqHXDhZ!M zg3VN;Zl!1~Z0*o4#Z&858gY+9Y1XSz-sZNC3G;&|-K$YJF;n+!&V?QT!Wn{{-HlSQ zQam0~)*y>Mu_*_ORb>`(G^sF`#o*J5j4eOONGszNhT?=Fg>Y?s_Xib7wpV3c^V|^rMxc zdDH4szG<*bvGa~7WyakK_LlM@8CMypS1Gh4f!VS{ZpT_q<5nv(usC8)Xt?YNfO>OC z8D>A3bLmWmIasN{shBRyTXgFWsJ` zFM9RxIh5!-bUd8LDyNE#-sT3EdLB~*>QU3wb+5a?<6(%7=jQpAT~iAtjftg*N0!BU zO{*R>AuH%*UrHQohXd(YPF)e-MmE+)2Cp^Ms|i$)I@c{nBzm}5Q>wXHe?z}Z=!}jA z_2U(-DxApW#$lZaS!{KhX5$aeaJcL%vMBH|7Us~*@kXg_rotTUSvxO!RVJX0+`9== z5i4{#iFNCZLDnm9Pq^G5siAb~#xQ1_g&veiSzVmpT2l957ka1J;6WI`jAI?E2~@CB!h146YR^xl>x5Mbu{O8KRJKGo0{6c1zyX9vwM*FLp?+;km9?P-|U~9ekXBJz7a;KLmk950-cow^r0Cyso_U;Uw4kodD{U?vA_BImWOIT` zADM@>H3dys95pd>#me*1eg#|YQsUXg(AX@T!iaoMfEvKML1ayPOp%#lBeXjO}y6w)ayB zt>!T}1Z*F3aQ^V@eY;kW<*BMRrI=O1u1bpQzf+L9)9-998Yv@anDpm@J&$^&8y?j> zO)Bx`PUvohskio-v$~A756K|vJ+n}yt(jQNBKAJgoB1AvYjAC?43R9X(d451qvppO zK=1z1=qXEc<`=yyKh)=RZ9`VP)EZmV3*|Nz6;ZW60!KdQKD7}~sp?|sP^AUz^}k;K z0M}!CTD-SSKIZEFNaIM$$5V>#$ihJS|67rmvyS>Hc(A@voP?GlD%UC|yYP>vGPMce6cj zLWD=B??~C$u2eFNetm1xO3OpwF>~gtX2^68?IV2h-xI$%+rSI-%_t|-(;Ms+G^uQE z&o!L!8(XuMa&hx@_pMZqE)b~)?J4vyC(t9)?v2H<-5VaYYR9V#%20m% zbm`KZ)zPLBv!vTh!fhSN%$N!~3dh;^Jw=X7NR}dKZDZOHaoh^DluV?jRo>$w)Gh8G z%Q65*V@m0jEHvq^YY=MsZ`!3pZrCf_RLU268sTwX$}gPU@Y)#S+au+`!OaO+^796z zE2W`o3kJ8pY2+ouoQ9!B%*J?yQj&_&#h27Ae9yAVL+KYuz@Wt zl|nB7cdv`9R#jzrZhnPIlqpA+D#+SvS!1^kB7DBJpDu=+$+D=5PLR1^dXGvhJqrTr zYgSVEDx#~J#5g8NE$^DXYYx4r=Mtutg<0pEg;odV?@7sxMH8i(v`r`kfITtIO^W6# z-B0_FyFfVRlh~w+zElF&c$4>}@+&)NUs5QhmKH4we8Z`xn_B~llCvK^qj`2dc~79` zt<;l5#*8BN8Kmk4I08j)xtoFYr3Yj>k-H|p)Nk#bOyD4HgB4CssR{|~JY2_hYN`M% zPu?_W(79S=n7+qj8+pV&MnDxwMQ$g}EsWdCq`Dqd5iCIUY;%grQcq)8MXgz%aR^B8 zzcJ5R&6}rkv~js{*L5H*h@E34hyYVc#I`yuqyiW*Jt>mS29hPV8^JPmzz zXS2ffsjUm6e7dTQO-@%vg8JW3REK#4@_SbmC{8o6?_%mvsY>!aO37yypbeq1_lu?y_F{Yys@cy4PS88^Uz4KK<5==4J zn9dCBt`bvfQamdW*1KNE&C`@r?;@6)Wi(F0S1en(sETOn#9|{fxlwt{R@+K)!@W$U zZH`6FQ*x%EtAu5a;ZDcnHI%A%I%r^pr!0>C-jMUbL$<&0X zw;7{YTA#8RCzom<$UwlCKAywgx)QQD6;;n@?j^JqaJQbyzGGkp{^<6qR@_x8Rhs2n zBR2lx<`$7chK0Be20<0iIW%_Br%q`;rVDG5T218K=13dVWDkG;09A9kyspn<)meOM zu4rh>=+=6eB^z`|0e*yY?^?yidYr9Q!)~=_LE*^bk8Cl(vnFt)MZAyHezmM@k>%pt zrz^K->Tl{+_S&0B7(d$)0~QJhKrxO-0AsM@pcLu8ha{>_o90_}MiOgUJ*BhE(i1d) zDy|0XgMtP+AAV`M#T`FU#MGLKS~BN^^_#25StUW_pem6ZDk}X?PkaoYT+)r~+R#|s zJSj;=TAc;um9_PmhH-*N%BSzA95=R4L0u7@^kv-GSEwCz5bD>FRNs=B)&N#t2I^41Mj?4~SQxz#f4^PRSY2s~u?_QsA4;`|9tBXDR zlkZ%0D$1ksKC3UwY2agRQUV2Ll8Af0cBj<|oM;4uT1a^cQg$K;R=sRT6sZ!e= zxlUIsL?r5bJq*njd)-4*Te;iiJpkw_MK&{sit1^a$~4y^Ryw%#D4@9(+1&W~hoy9L+1T)9 zg_PmX&@~fj6lUnC4W5I&Db$Qr*kRRKzh-RsPe8WSqic;`HxRe_r09ON@>#}f_9?UP zxO*c10GHKK(b{P@8e$P;88Cx@abFu&bDO-6&}qUEii#~ke=;ARB%jW*=ViKQN2T4Z z<&TikmD|^KZwJj2nrXsB)+-cKl!*+7_=p+lPAMTuO`<(=<`w}F5lHGW%_^21JCxQK zjj(4@t)J4fQC2j&xbQ{Q%4R%9yaFlc#j-bNfG(V|#f)PlR{Xx{&ZqAj&Y=`bf%kws z*DRH^I-@2~p4De{dty|9k_}C+YNgyQ$%WWV0{Y}qu-}lo;4&`%0K5Lkr(tViyb)Ym zOC7-@Z&8XPbnaA|lCf)FX|)?qJ%CW1N$O2qNposNDDpcP+GK%b+bp{f9&0r9HJUGK z7k1K2sc0F%9FyLOTcI#c+Lb4j(ZP7i6UR!M7Hes?sVpqhFc}#YN-3GnR?!qp4IHdv zC6~5()E1tFYoji3z=58W0UT~1b(0uw*`^k`aix~$2k#Tp0-mg!SWBA=sBQe|Vj+8+ zQ*mfYQEtRAfJRGgH3+6FuN!v;C(@}BbABxF{jQ;E(hSEsa;11R=wUI{C`od&gBgLR ziH)R>JBs4w%Jb~kkUGYv0oZk~w!mhQtz^&7^E{e(d&wG;2quUz!Bbw=ja>QYxx!W{ z6ofX|LWa#wnEVU7pAbU`+cGZQGOXiDbVU);9R-j$+DV>GHq-dhmO zr$UE%56YX0Mv=tH=)_Qojr@IT8?f7NVz#GnBl!><9*ip*vbM(fdVJcmo7D9w?B@l* zbT!2~l8Zf@J{A>Yrsc~yt@S9p+r5+X;5iw=_H0nexz}O?OM{Lsh&n* zjGiebm69Xzm4&t12=d&F(Zl$_K`kY8<%}jqDxdodnNt6Z6q=lQPb1$ zr1{pxQH2_g(niLCcrWaP#wLzXF(6P55770lh)bDV^YOGD3Y@pP=yE?0S)jQ2B63@+ zFvbVdwQ}NJkD@YebejotSHswdi6W=91_IEa_)~%@Ts$hvNvq+HL-zU=vaq>a~#Yxnv{n+&H{{R5<%{t;ZFC+U*TcTrz$m#DyT2E8x>(Z3zzDKF+ULB6! z-D8p_D~uJ)B0@fd@%h(dwU2|v)TrmL=WoELs_VDUtTm;Dys^fu^V~j5F+EOy9<>*1 zM6^06;Awj=Yg$)tq0?I2hlT{Xh?2{LfNG~d{bz=gF*#-br=-!0({OT%VBNt=X!Q|Dkl;cuP%k=JfX0vm9t7#7M(mbB?v7IOB;XmCO+KO@LC)ds($e1@1ZeH$EYWYH>8uxnX0_*G3)_zmezW6)3p7?t2c4Z+0}+SY8lzec(oO{Oi?8*!=SqQk$!GndW-V zm46-NklhFSOED#g>+f9rlD(Psm|SHUQ&Ok!op*ttn%XdvNroT9aya}erkq?>r_bjV zBT?Qc=rye>N$w-P6CW)AI|0ZQT(@^ToGvX>i>B7Bw%U|BZKs&h00;njpK7Ni6?2|A zX<{eJksZWVy5*p6FXlX-dZi^KR3}pfJ6z4RwKgo#2==XNv^#26_V$&|C&ao;x3{ih zjBY1`TsVp@rpMD|_{h%**(N9Ew~F`U<0 z(D`g~a(6R)LEzziJKVzCl>Ehe=CPFpEl%7HdF*1LBFBZT{=j^OLNU^+G37>CjZbd5 zlQi{1Yi^%vShq}003A(DT9Kz7W^v(TRROHnBjkYg_cihP#&F;6 z>9g-LE+0_+?Pc;k4^FZXS(rt*KQG97V!mC?%T|4?VxgiUw7;6%y|lO}Pp3-FPQ}U> z)f80674ezj635+rDm1ySyrrAT)>+h90Umn}^`m`=N%bwks!yl9aRnbXHyWtQ#5#P% zTf2mtQ{6rQC!cDWYRiOe66}-0!GK^?ecz=$2}zE$kySDr1NToK#iK-C7HIEs{7V2Wa`Qc&%i#FoSp0%bLo`VJ>AG zvG;2}Q&l$IikANX=*_uD3&^QOy-@4jL9Ylh#efGG6xgF4QpSx1#pS??659_1im1iC zh?`MYX6CCMj*zO7e)47<4@%NdQQX24l=d~C zcSnk<^1;GL>uj?2yH|{g?uN(D*7s+2Q;$shB<6j^w;Y_+C1avkziS>)xYT2fwj6QR zvXx|q3@oIsWnM`05_j?0g<&^yx|CEp6*OtCTIt}Ke8~F~Q3m#BMlPHb6yu{aJaG$0 z8njXM=qn6Dve50x=A`W=bNXJTbzln)RG*m-LtK%oC?38e3r@M(=vvLbqZy7Yt19J} z72is-n$Y>IW(Ih7%_7U+ED>Q73a7POsbd9)bsnsCoY7ygxP`F5;e`=udZAZRq3%ef z;+IQRSto1*!T>pA?VjHCHD4~Q>C2+(-Y_yH0$UyBah~4Qo7M-TA9kgTNfjU^kIII) zq?AueEzi`))FweWSdQ;fE1IO$?2k%?H_LeyJj@UX>sh&*@|UgdXR={6w5=Nsc<=SA zQgPKB)1?aD?A6ZG!+PXe%xetDi8Avdan3t|UD$k2w9%hAkISWmZgABb_994b8LhVk zKi$vtuGPMWk5#G0(=+@*Wi{R54I8sR-B!+9-=5!ECk|~6%oZXvp>*~cr+7vvExg&- zNihWDvX4RjH88_QD^b_Zr8)A$MfsgahM|TwOJ}wcV1TDANgkM9wX#VqPahX4)R!`U zkyA8d%_#-3*1pU~oAZ{v*_r(>xBsp-+`roastZN-F8uSo8gV$7wE_EwrgOT)*Cz zleBj^J?olyl+~lT^jJKCQLE2gd#k5^_y?U_ONox+%;e;fdYa;L)cWW{-P2;loY8N@ zjf2?LLQ49XRi`UxbaqIF(BOl+(z_y)M~zj!acnFtR#t0;R&Lo8;Z8TyRq-_(Z3A6W zDdfegs=hw&YTepeBhPY_7wW5UNcsb6zmRnu?Z?6WFK zC>ZI=liIaSJsIbxUWdO85u9{~)2=cbbZ4Re02*&&uj`7uZEZ?kEAb2(MT0>eT4{m8 z=Z~qZYE)21PFt4G7g0&=gezkIuA~X<*E*-J7E=^^L#KMekBC`A~O7rAbvD5e$N0Qd$YI>{* zD>{6zy612I0A9WyH_n|&dpdg`rsRA*3K3OmFL>-r!;(@UnLQL@zH(JA&#P4IhTL7u z01G30oB(QE%TOVi)z0&hag+6_xYDsYBS!*EBm2WVjty6siLEUmolNT}N%6wfC2>kt z(9NFC=*&FX2>|eOQFn6FiM^uewm0AF?hl*O>s!K_k*h1Q8Jdc7F!9X5+HC$=h)J4 zY!yoo+t^1OY|khTqoqowSG6+b^5mZkKM`59u56pJGkF34v7l^q&suhkfi_dQ(S%ZE zT}DquJt+2e1zPIFSL;5X^1%_8cTzdUOp}RGYDF04IsNJa)t${ON?Kcq2~+`n2TBn3 zFRhSELvb(%JONrL>2o(dY!)}SR;p6fR2KHDgt>Mia8lf>cxAhgJ;6R_Lza zb|+0ZLa!sufCh0(kWOPJ`ze80bLL~9rC?3mjE4kn;C2R!jG8u)7csWdc*yNdI|-!A z15A!cjEec2srRZ%`jF>mV>VfCf%4>D1xsb5wim-G!h$q-+im>a?Ig7LTAM18538j zOz8C8LMbhaLdrnvgVMgbm1QX19|4HNN;9;GF12-JWG#YyX-c*9HK&4U-6Iz5B$75_ z+2evMno8%TN=hvxbJqIA_E%yF8!@wBgIvn1gR?z};9*)E%X6)Wrct3IY2+}jwd8#K zlhsJ4;v^e9$8yIVPimTpSm?k@TO@h*p?eUW(kvDBuO_6`?2oRYLQW~jT^mrivAHwA z*!3NVqMb(vbQr8OD?^q?Z>DP2nxq>6RJd*m8tpfEG_Sl(Z4MYMBxFO& zedC(wrj&L({AA}@Cd0(G*7vFyuiWl)l7AX?5|-n@Rh=CZ+~#iXtu5`$QpyxZ*qXw1 zAr)rNYLyz5CCwGN#p;@a-p|HXAbwT;wc}N+>PyJ_Ts|35kC{_;4?Qb3&Wl{J0trZA z(>0`0dYH~!x)!dKh`{!%k}#(3%C`3pXEM(Vl#x#aR(#4Dof3kooMf~+KMwfPE)w*4 za}JUYjrH_3-9sL>k@L9hiM4uKuE$|_bVL!$5GGOc5P2rOCsRwK=IPP*+-0$uKD=R( zOyI_({o&fP_V#+6u*{#DYSGSKibUgWIS-bIfQc#6|T zb&5RAbu!~(0;6wEy;VK4lTzK4jWBpA;#;24U)Rl-w0%MK4L@z#MZDmKAOMTsfxupw zCnVqn9GV?TMc<)~Of5Bd$9wK)Yx<40k2G^hbF$KAAvX|*1J}6vW~y~kS8l_?;HQba zVH>o0kB_`zCbcW+x@^q4dK@twWj|j}Z))?aR_At($J*riI>JR&tiAr8U+_+IOoRr; zGg(0%t|Fz#^$2kzKTJ}Zvo3`6u}RraL(OK|I#PX0dS0S!Zd1;8^1I`;X-=;r*JGa% zhE-tsR;Od8HNE2oR}7?e73jgndmlT9jOi;hF)eN))dkE>!XSAExS>U-bQ?}u4uk(Zhd2d zqwd978y+06TPx8!WtF;t+P9|;S98wC;$3Qdr=km6eNN0n^D3{*82Z;zw2zNQX)^H*P27iCoW$`hg}X^JHXmZ=8Lu}RkEMo7 znVtDwU0)e}E{{p@H-u%=?S{2{t0a;E^0>%fVP6rM=2OJY^86?189q@@1x<5&M$=3*v!@o0 zby`WB_OGL)4yF^iNX`ePVLE)LRXV)emFAhegoN}{#z!=-rHV^ij!QXXxBFW#RR@45 zNE=tMZj7S5;DjHoJ9ie_69pFgE@C7D$ib_Qnvo^UtZJQV8FcdtxGcj5J*#hOt!8to z4LhTolGI#CGg^9{8)~ zafy$!Q*6h%)fVn{Xx2q+bK0_$uI_CZSy*&(sQ_eWtwqYQ8#^&1oP8-=b}mI{Ht8B? zD#~(88q(70WZZfVx7Q$un2cgfj26XTIGrcZ%Shw6ye#dtOLX?EId09a;}fTSHpVLt z^lzB3;GbIQ_DL-cUu`EXOzZt3^4`?DO}TN9qpd@Pk~EE2xf#(#HKnrL%nI%}0Pt#d z)Y9p@B6}I)%S|c<$oWB`KA|M`Bc4{8;K?Bhos?0{a8a@;+l5(P<+%&UT8ou!2rh1> zl1=gsbI?+98i_re@<||2I{uW?xTLIXnZ>k<6Lv`C_N|tOJ69PfjXrsoJGvS{GFui?9g`!sC!?e~XT`fRH;_HUMZA1R&Wv8=hN ztx*Kg#i`ncfyPhfE9+FFRbDLkxVhnH%QRZGhs&}NyqPKpB-V*pSi+?Fs&gc8n(dU2 zc+)OO&jPqB(|5W)rw3g|nu|Ig1q=1uv#BxTWvBdozd%9F^dqd6&Cp%tKOKDe#=QzyzDA=hj|Ul)s;ShazhkJn zwuezsvDJ_ak^txNuTdz?9tAqH!@X9f*M;X>WlK>DNLK-Ioc%B>T5?fVJlw*Qt1f%) zbk{n3dXSRj#|(0VvOWVBZ1aFRsP1}un$j{?vpn?YNqf6?G48edJu=SW?xaXs-yx$w z7c4yh@9$GOv5IY-6mt65ijh>+`MRD#<6jY8_=4?cxFI5pVi<=32h-NPTupgPQ}isa zE~kQrEOl>obJ0%mNEvzdt~l0|WO}luq^!=DLt!ZM03g??Me}NSxW&t%N5oRPT=^;p zUZncg7@CbcBdY-f;mpjq7?#1VYEy05ozq$lxZqXPc0|CNP1I+xebG6=>N8bK5aF?& zBMl15(nq3c7tmi=MDa)&v4>vby}DHuNT!dE#bM_{t5QdE;m{+t4!^=T9FhR~*6C<` z&N5BR;(UMOV?L)HwwU>Ni4sf)Qhh6mzADW|C(~s47cD1Jy5FJ6>RxQnJbV_`&x$~H8BgI$b=y`$E?k}~H_duU9 zvT@qE6#1p8_WuA>g$fp@(Dc0zWxk*09G#;eV!NWOkBY`#rCSzsOY7^Iq-OyT@B?+H z2{*ZpGY>|bQd+V<5ns$Dir-GOl6hINBcgD%=4KhCEHmYAQ_{(DJVr4&+ui7Qo)^*M zwbK*LL^8L`&5mr&=JYL>agW1!C!$Ik6zyOCbwr=x57}qcI4m!eT7#&$@X?;o9dQprJH3|e?m_=s`iT3GxkrK=2*Ha zi?TvV9E021vvW34MHwzj$`q;h;|r6=f1YcH^E6Hpd-SrBt}MmMHC}MJv1SW13D^DMrrYwa8+Dup`^KaxvbC zq=F5zB_2hDosFDlAXauYu@$Yv%;8)vJJ2?cuUJp*Ir6dyA;vS%n&_Xr^f+nW3sare zkXlBtTf@0imYDQ4i_=4@66VxP3+RL%WyD`G9C3j{w@{LMBTm;*ifGm~Q}Umb=Co6~ zv5Tbdb5eb3C<>Gc;Es13;I=D1(SskC1-lAG73GMjCB{9w&@x*CjNY{2=pD zDC%5ibWy(W&9=2-_g+rOkYR%krkYJ^fz52sDe;en<1CIQKI+uY(=Y9>BEpa)Jyp7j`x+RUb)$H*;PF^WxOrMgN4B*_CO``V-nDjH ziB+i8ko#l%-;@9cLMTMkT52;}Lh)`V2)$U*C35g`v$s=~y|sc=E~6OeeQTbjD9K#+ zXx5xvnUv8o0+=T~FT>OBI+k4`*r-Qz-II%gzY5TC$>vULc z?3c)fAVKY2w4$M+K7SWdb)Phtc#gsw0K^kIO+N{_QP||GQOJIxGY~aZHYSI}WxPt&$myncDYP~b<*})X*>Rxp!tgN$*}nxty-gzoe;Cf`v6?v?G>1;0*m}lDaA~vn7Vb6oTB-g%q_S zc!u^j^k~AICNW-(7~5mX#mT+M^=o9E&h_a^tu-yn!qsqE7*pC9A0wr4N|z%&SXTCk ztvK9xtmdw52VwU16U@V8Y{2>oaG>R}6)_Nk)ty&|w0o;rR$|3D`Ip+gOg#xhR(M$K zbvm(e-1N&d`(~cjk+(9TT&Ot%9G`0LdaEB7QMy%qqgx(%t@ujoOuS&xmQZpZ6~inm zi$1#voYTa^Mn`pS#>rHVnDrQ|ls(%QPNMdWlwt7XmoBcvs-9~&;M|_4O!G+7R!2vp zX^W>$jS}R8&%Jh2qM@JQ0=$c+D%7WKU_R`D8Q4&-+~sJBh@S zSt7Xvh1ySH(u<1C6(upmR2CyA9@G?TX;VRJT1i65k%8Y8aI)OWqHkk9{#g9&(Z)bL zk^U7Dwap@(wIGV~%g8{=dSuY0Vw^@T#@7ua5)WJ&E*z!HQysx7Bmg%Y)7+PIZCTk` zPh{5b3Kt)CMLbrKrkd2kby{~Jx6vT5pU;v(CvvBy3YDF~*K$@yF1IVsIcS(D^c^cX z@1fC2X{kacxGuOHuQ{ylX=GWwNv0?+812Ut+Z0k;47xMLWCSR#hqig5`UdRCx{*qT zbF}vWVAPrA0bb3S5D2|L{97BGc~KbE9-)O z&JhN7B8*g2l9SNZoMjcMl8Dj#$tF4BYb}jhMf5Lm0Yrx{+|}HRS(9m(X3^Rid!B0H zD-k%~Vz#$E#Fs!vzXbfMI#DNZgjJ2*BF*EDKRP)QN5%maQHOInlU)iLm77~%AKr&K z?b@@HY|DhykZra^Z!m6l9s5&B3z^)~@a?^Y+%8riWsi`?wS-eLt2rXAoJ%gO>32T( z$?9oZ?##CB8>2s2wYHB?WsHNg@ImWSC1zYC+(c1tEy5W~1Xacd1e%fRi6x#Vlgr+r z7oesgu{tft^5n=JG2WVFwRBH((Oq0AMPsz|W16P!$%?WsUf)S9X$7-l6kIZ>>T2Ay zv7BM8*~@D7vRup~oNokhr<@AS$lftlJoCg}9a{_MyRx=-a0&S<*2{77qcqz*eA_OL zCXnCPK||_a8#Z_ueYO!h9Wmd=hbLW2~yHp7W_X9urY?rPRmNO zwL8cX$UB;9oK~j|Y-L)yY=pj^;t0%<;|o(Z*2Ph$3XaU1K$Gkybest#fbRGi_?bkbYOZxtqnK5r641<%_hO|3j zZf9c~Q?ZKV?o|Q(tCqC|Jyz$Xhr~*rk7EXFc8Wp>=xfiE99inZs*<>|2lGbWl#+K8 zn&lm-3`K#>6%~-M`m-`1a&j_jnIpPZEJJ3_GEGvX*^KL`nwox`%vLz#W_Ia{>Ze8Q zjvDx+wUf}?)BHImcU}C8oMXLp!wYYS^6Tbt+McIjph*Rih#eU1=)yw0S6w?rSo!Q^ zXIb+n_!~YT@YFiurNcz9+>DK5PJK!A<2;&|7}U|3(8g84ZK`{tgw#AOb8dFe z2pQ-{ApSL!u&=4@(9PjfShJtL(Vh~(E(q;hv%}c+=;M_7nfEcv0t^CAr9(|4t`dyd zx#8IS#0`uS+PkPLtDasuT&~1AvdXutyKsVvm*zCXIJ$T0XI#W)V5&2k!lZe$J1NhR zU6(9;GbEqBlPWRY}Qol{H?k zyzGnGRP%j<%)WI+QMt2K8FH($mlIAt*3^~El+~ow^>)*=dr2DKo%aKPJ+od0XPwfb zoM81nyCvaj)!b@gHtc#vfu)Tq$x>KIR#Aqp&#KpzNjD?)dQ@RfDll6YFC16)b5G+FHvq?THJYLCqwtQ)v_VeoKHp zSrqgljfH!n$uQX{Q;%M^SvMO1kB7)WC zA(tTf8Z{>A6J6V1+J*BKwl_@E)d@Jgh#?H*qS zDgb;ygTH6DdZa|QH?FO;h~P;>mwaWJjc(x-V5c_hWDDrU<}JdNQ}s2UHJPU?Yf{#! zs@=|FDAL9dV1veLZsXmqKW%%h* zn!C{P^DMqK;L=*N8${JD?(H=(>-gx zO?5oVcl#!N?8?9DK>RBy$#gpDLBEK?)$XpXfS5VXYm%g;9Zy#aMx9S~#QILH4yeM_ z*AI-fRXWP1hB&Mo@XMWSZs~C)yOoA8RGxryT`*R99$q4DqP&?tsRS}9X%#%d+m0%k zo*Ja0?6x?~OIfr5;Rh~<01oxfI`LW`Nkaos+Fco6PI)Frk^7DXDs8=4t#ueR6jzx9 zfR+Ov&as?%w>E^Fb}2kYHp=v_X+g!E3Vh2_E0i-~RDDfnCC#CwIdW>pTWR~9RabMG zQKNTaYGQP|*y*(Y01q?TnH&Y@y?4{YE?rL+zGS02BW4RYG^LJ6*Lm*8*VEd%6eCY# z&YgMUuAYQfV(WzU0!OI=69JBT~`g)im{cgg_a-^o{q= zR{REXPeFh=Cbv_ww?oXSMpWCBUZy^=;h0_CL{(8}p?PKJs zPE@NYrLj{?vrQsaw~>G&m26`p`Ov-H4va;2i(KfE9}%N!O=%jLhJI$wdUg~ln%Ifu zlJ-iZosDaoNU!bHzlFIxR_j!G)ay!wnmOx@E9_UHqKFT}txHJuVT|_F?Iv$RHrh1T z3ac6}TRo{ZqOQ*-de4>{vnxq0;$@cFC?tx^za)%q0N2T8+2mARDre}NKf@ALV~DTQ zq1|a(Od3t7w}K@Z^d0NsCpR~CevcSND??RoWoVWuB>7jMsJT`|G9t?2zeF`foa6#`{Mh)s^Ii;gCuBW#a;9x~6@I`3Sv5a}HWluSClegQ}vS!gF zS#3p+&A7}DZuGRUYjTNl(sbjsP0egi7>p_2c9Jqd??A3ZLKYwb+~8u6?r7-A5?w~a zcJgpmuA0=&tM^01aRj+66#Z(dMJ6gP@iBK6l9#QGLU*`eXK|$!anzDXp{rip$#_)A z`$OR8`Ow~_LMp}GF7i~sg^M`v>q2(eQi^4XY%Z@S%3Hd(spBS>D{Pf0#T%X-@cc$i z*HWhZV`lEAwx>q=vkJBCu!Boj^=U2TWn@p9m$LV)$L+n_n7X73Eb^EmVT1RDXC-H3 zZ3m{MRf<#_o0IQIqHbBh;J2WrNurA!P_UUXlxi!6-myu3%Z;SHUY{Z9UKzey%T=6>g@+)D_GnE>T==}h4(QFpb1UK& zsc{Tq-o*&t_oV*-3|3gGjVna%$ncPx=d079!9xtCff+5F*PN5N^>Un2vDsKCTOH1% z9lfj7g7bjus&h}(gna~Jx_Y;RAUWM;MB)fucO$7 zugav6DuRUf^{m}XXlSs>wI{ig2DHCwFidbWj(Qr!R@Th)CxCpM(I>sJhf*w10HAVm zcr~XgnUx$wu*+S}m%~kO1X0H%j;xpj?NigfPfF;gWUP4Db3u;wL#z1` z849a>?pT1?E0+H88EIX!-#ja!mR z=1U1)tzJa6WZoC?4aTK5=~wq;9vEkjQhiTz$u$a1tHje6f{hlGnX`9i4xwyb*tm^f z3Rn!W0PZ|?9ZzA9GHYndn@MhYx0MNVC85n~8cf3DY>S0G2o+w<-1I18W6N}6Y8q7b z@&Ync^fa9|k4}az6%ma*%^l!^W%*A=6%E}LdpOPr;cmTg;>>BO!+)c z8m1OaO=xyjES7qEPjJ3cpvi1wr@d>kyFPNXU23Wj*5{jezr{0ZdXmW)XzkX(Pe6?loT>D%* zv}{cY-*4yK1xy{d&QI2}v8#4=BfGV>S8PbGzzkxG4M~gTr`UvP7U{FNxId;Swu8I5 zRb~)QSPz>R0FLw_vi5^@xu!{FXi4Qt^s9sDWmY5Yt!*yabz8zGQb<KrRT7A6B6e-$QLY^`_pm_RHyp&8?#scf`YhGdpNq10#6f{l9{K(i;> zVs=dR9+h#9x|vRTXl?2j78ZI_Tfre<-4yj&=$9;FazRQ{yEzw>NJ=Rz4{GG*x!#qB z+RiN>frlr8M@kd0nz}Z1ogTwbjU<=-V`Fm)-P26ujcG-k)VfFZY_fS0J4QMw9ZhD{ zj+H)hOGMD;lH2=2`d5wt^K;bGP;XKvRVT`f>F*O|c@=FGooE67PxM*inQ#V?L8tO!Uk+)~2Dq7Um zo6xrn+E{7o(c49pIS#{`q|!t*QG8CJHGaN)HYg3Bbr1_tr*D| z%?X8xpgv+`HV5^f7|9|&aaRkHF_X|x7a-JO46%rS01OkJrh%qLE#`y+w>yXBOjkvl zyGYkePqjFc&TI(%2dSrKwIRE^9%QcDhx?%FQS^-|B1Za{OYg1e?_OT+0J0QL{gGG60vlL}!9F8&%<4!h`xs7!yGv!R-yix_V=|D0` z>zsjH7=)CMsLP+Ur*h|nwQ&N;XLBJ2Mg}$~^s2+v<@Z_5nc$q-sRg#@pm=c$${Z7r zNAQOJd{?0jPm{*WmrwWwuO4`vG+5pUCG&453{V%}w>?R2=cZeX{VH74BDdKb9EF~EL};*%yU^rTG^jnR+8j$f$BaNvb023mE`h=B^7a>ZQX@-a_OP* zIP656?AP_^Ne;Dlb*jCswj#HL?c1HX8O{JbvqWCH6H^NaQB#|{XHym3yo#%H7#8c;>w6;ar{3^)#~x zQ&w!!(KJWXHTX7!hkD&nuXtBq%CIefh79&ofxmkFkOJ zUkhXt!_@lKPOD?mv@6kUu-JKJAS41?wS25*eZ1*)H}yGmB$h~@<&}ou)hz zOQ_qjB)ZY3wPCZ(Ss#srb*pfaA~j;m(%LqObR`Ms2-{X$X<CB% zfN2in?P9YqVOjq-xErJhEkARZQ{eT@I{&}xye~3HVj>mo z>S`qUnQ)De>&-^$_BDZvMF<;00f9uO-hmh-?nQf|*x5nniPlDS`B8f2l%dM6a^qFW z+QuD(cTrs_5f%ZsIO4HV)aaF(It$%;<)r&NOh5w!EoW%j`BIWbcoi+0s}pVVkwQ+`AZ{7p zQ@Jlv#hl)BzE_*_D)lFWO*@j}w=U^YBzF%gKFI(fNT(NOMAg-b7m?gccG8(zQ@igI zNxQP*l3EhktV?pUGP63K0PjM2iS1%tV^tTISd=0%t});7rzgxrDe|GV^Qnk2JPx#q zXumAhv%rztL^^WEsWp0ZAt@)Q!=~P$RfY)|k~jjND-)c1kx9G7-+d2@roQOPyihQ!L8u9?S)!osXtXy!D%MtBg| z-^vWx;FE*&uer;!4{c2;Jx|WFY^T^&=7P{QX)Z0}^OWviO7D|sLom-kO-rVH;NoLnqvP0$Xm79RP;-j7 znw9K|79JYAGI_U3BSk+isjhfoUGF2VzH3b`T6(k5JTn!#q-xW;x{;704WG}C&bT7|_rG&I9FLC|{9|nOf zt-)PNFDi3_y~pQQ3O~)c>(tC+RyU&Vj{C!4uEY>2Y+w?n8Tafyy=z%$kD0{T%T`I^ zp{Ll|4MHLTg@QRCeEk7Go_OuR$u*y}_io39gz8lGE3e>ky03~p*X7$-?i;_>N&N*x zs^3VSt{W)!SDjyyJ1-5~`F04`AHB~3eR%ip@6ZgMPAg>Pq2c18Po3y&1y<zcrl3gKjbx6zy}qhA)*X?sGS6ZH$OwVYK>GJr8c0_Lg@k*T*x; z>W$N36;#q}nyyJ2nvSfo-rK6MoW^qP>+M>~X)~TQn@p+V4Q4y|_K5gX^5?HL6(*#O zXwq1;w2E>kF!s>05T6jMMq$y`H`0%qT$=jh_7JEcr}+fuOYeN z*_Pr`L}?iP@?M1{$SsgebfgQP^MW(aU4Vqjmgvvhpdex|_T+5zUv7a}RZd8n@7|5x6l%(uy zTFVuXX{~->Fiv_msI5~`)fq|3S~HrPQQu}-V6pZ{q8&b_vbnz_Ouwe;I%nBg+_Oxh zV&T&cJ#V%W&Z-DHjxzk$m zf8D9*8>y~}6z;BaVrufcBLiQvv1|7GH06jtFbBP3QYlMAqKw;)q!O%!BTNJcdgh?A zBYAM_;z<>wkD<@>rozRDn%?R#g+V>rp{cNHh#OCFI|P$*=bUyFSgv80DI6>WVX}BR z6k5QY#na-ME()`VA#H=;$Z4f=B_xrl_Wdr|7Sh2y%YxWx zi0|!Jc%@`vX}5M^>-tKWeAs!I9LRIQtmLfDigh|OB`zn3`C9={dJ2whO>NZaG-tII zk8sLbMf;;X3fdOj=c{|z(75qk(?%iyMcZ&4YhP+(^=)niV!5Dz`Wbql%q5Xmi)Y!mMo3#M{29 zVSFt0WDN&JW*p|ew=T_HR-;h(yw@gFV(L=d>NIHfw)Ojj=O9ysRe zKtYqs9OkuWEi~iQ&$?*s(|E>nS zSYl%}m$b=nrcWjZgI!Wqv6eNXkS z`dFB~_dE<{T~-g>)f)<6f@3fnq$ey-TI%LV^Ittk^7Jdqsar_@0A!K2?>{L#9Q#yz zRP<)i!pk{7!i1MonlvX`{HsK0ve;`D0NyNFEtWaL$De-0lfWGSu7y609(s*TJnv3cW)n*W zk8|W*UF?Vr>Q7RKq37DBQjN7ceVt59d7&MDQ_s9vuUu<)Efh@4V+q7d{opzu{+&k)s>EilYhU4&P|#d^O>zH4CeY-9v1a<#$IJUV^?4I?ZKXZk!*wexbv8 z9B_1_Qlj_Hw%#jiYdG!VAom~)V!m;^pI()V0$~)v;!pq>JGspQu<#Y4n(X7R0B|!; z%rDTS_izUrl&(7Gy-my1bF7o8krYUo{An<~i59}t*qI(5Dapc>Jc?>M3C;6Zq#EMY zN~5zYdeTL-nP*S7ytqj&Hk1Ji2FFqDRFfu6Een?Vd`+e`w+7KaPP};`e=POq`B4|ks7@+tQY(!{Ym0+3Aq9^( z@Ay)3y9Em(#205DARD@Hd8N!YiKS@~6{JF{;){!@`*boDjv@Xu#L1I#%YvjZ^&M&4 zmP0vTF#M#3phmTn@GYsgK3Evw`qq)PtYakZU_+RGk2 ziqM=wB?-%w6>H3iPUuG6scLb@WWGwVHG$s$5 zQ(}E_pwPvwg}b7%^C9B3_PnvOF!nt2yo@a>J6mWZn)2RRi-WKz>V3^)rtImHdm@LN zOA(L(I15>`REZkd7;M>wKE3H(jI8j_7XT0e?LuSRx2DSS-Xt-cGaM4#1ya?MC1rDC zP_-5=tvqVQkID(_?^feysgz~Sv7Bt~Z)ck5up3u6^`&OZx{iewzPOIuLojWsP6F}x z)bnUxB5veXQmf0j5s*4k28+GHj6*&5OOWO6VgQ+s|578f9x3rj6Ml z$xL!H)|woSn@G}OlGNSm696&tMmn5U(5H2Fb5yF+vJWQnS-T%+0C{V%MlcjnXD6Vp zqO~*ctZyu?g|o;cZ2ZivoKk70Vxr?Ea(V9KhBdd2;JY8cv~WNA^zUs1mEFrrr(3n6 zOdraXv-i2<*A)2`h0m>wvk{InADHt1d+~~viza!%fnbU(40FdUeza*~Z0-$nZw;50 zwoGnM@t)s7RFbhRHqKAS-VR+)OcJW%Vw<-Pwb4eSrqeO2O;lDq%TK+%(XWP^c@Z(^ z0#Z1|eWqEODXvK~;4`d`mi1t0UEa+crJ@^@kIYxL>b=qP^r%Krw2aieOWE2Sm63TW zdy31So!ISk9T=%}Gki&Hw%c%1$9l%Jbh+7pt9e+<@ZPxp0B(lXX)^{$$?fS}^f3>a zMeKLu^7ZW9NUwP8ZO@xFGmk)G}@ibu$3FBwX&5k|2sw!!@*_X?h^ZVP- zE@W76Q=m2D)Tp_gkFBGJe2L3)J63g-WmC$K!28t2>vHNO(~CPjBVB`0ltVY}u0Za1 zZ>RWIp-&K|?Dsxz6_-yB9%$@2;aHZ_+bXfk<4z& zMnfrV{sV*f*Gw0>cv$H^X5zmh#<`+yxH{BLE0crHF|_`jkJlVkB%i(1ni#hT`w1%{ zF7;bWJsjTN%nVR}tBF7$lkMy1KDp`1DX6Yzu{5xiD%SQ?)~~7MUMSXXwY@>F?s7Q7 zu5*ArPpx^iYB{@~bC=+$;OToPm2|u0d84*<`|XX?Q>d@XZ-~RS7d6oA^w`7~N#$ZW z2SNu7E6`S2Bj)iEbA#s99)qRa%l2sg(iF$L=RHqOgSYv~tuFUIRR93Q&JwP!lkm67gXGO6Nl>Qi4mA{{S?%*E9yG*VN!xG^Byl22cqIIT`h>8njM$ zO7k!_hem7+4hSN!I>fhmVcqI&+_$Q% zL6TcT6D~evt7#d*x4Cn}pJtKvT->r?@RHG-WIjfjJzhe1_kV>y$YniQ1)bt^Vq~GQ#^8)g7LK{Ws@8vL@$U=}*q39~P z*_(p6T4|GAtIG364 zxZ8J*M&BOj1k%$Er)vc_U$P*kTwLRTVs5k)zc z5vnk6*GS}dkR8%Mve`q8Uk>~y#HFKeeo4Yt^A#CXW$ zgX_h0yC~TlTFO^uHi36%BfXn>kG$Y07%f@C&qixCC8@D!bc+lB0JFzzqH~P1H$z%R zFpDyioR!FYL8$5R-3TGJj&~z2_{h)oqHZaUnw^I}qh@RtDdIC>&?g>sr%R)W)Q*qGoQO z^GfAYD<5!bWvNlnW|?KEPcF+%s+swnxTgrWu;(?@)VR94u!&-x*hdgJ^8v_jde>B} zValZLTY~a9E#pgRN0~Rw_&CKh?6eb&-laU4?^T#?A@?@p+NZ9i+d|!(HX6pMG6Zm2 zl`1d+^`{uAG9@Pnl<#7m!gCDL7j-{)_Z2cuFHyy*E1{xNVmt7mj~VJ|6lqy3c1j+2 ze8(%feE_XslGT|`^3f5?a+7Ki>I6omNL7hfjDL+cX6`FAtZ7_nQC>vT8-f8sLiELI z;d7tb^1E2&JW25W&sfkN;@yOo1RuNWTF`Erx-!HySKViX-hH>jm-1QaaXZ4N0f_1K zucpCfv6s6e<#Qa7+n$juc9#BhdXzwAz&|gcuX!ZxBzO|7K37Iv1@Qt$E}X%BV&8>2 zkxn)i1UN0kB zGwc;uwRB#$k8Btw+VMCAdZAq6Wr;Axopnc!dju8!^oC$w@SqBW6KWT zTJ><1uM0P+^EsXqlBYCRx_3StV$z~d9_C^O21swfa(mx%HA@ve`Rj}07J&j=A(NoYj<-jiihP!ZU^CB zWNK8K_nG&;!i5ywIHcL7b#A7_p8YtksYl&7snmYZ=b2s^K0w5%M{-lasc_ z*0Z7;+qmx<;g6QTbaDrO&-`lKF7A1_Tz|2q>8S2l(^g$f4L3`f7H0d*KZn$s;KyTQ zhodP)>UU-NWbt^o#%bC9M%RP93#Mtea$a4{b8~u0!FMG=^{>ydIQq5QyFXjPW%O|A za>rxUG^>`hfLL50VHn6Hj0(aMva>oT4Q`CBQ_h<5RtU;Yaj^dY4P_^(yb`vhST62u zBNC!wCp&&+&T12}Z6<2Pq~0VgcL0gD5a($bs^xo`y0L!gpLwXf_Xbu&oMF^=H7<85 zFy%BQ(rs>SeCsK)+j7WwBk-rmb#x_Fr)Fi?*}RjLjAtH(p(~p?BrVK0FwDq61a}y=U{0x>l9(}q>rNZ|DpUc==~o;%4cZp7JEZl~U#GeU4pSZ1YknuOcj z?ay9%Qk1SZLzP(0dv!L-&*;X10!(fA4=6h zbvflJ$@{w;{q2k{+u~qB=Naa+aW_-$Gc33;J4e7$pq^@0h#sPd5!qH!VwCf^91%$& zT;8^QPS(Up1H-5ygOwzCi;w49M)o*#28&}lvnv03WpqS3KvNf?O z6pix{-`b|-B)X8;8BC}?VoB&|K_skomeMt`5IZzcI2j~&#dXD5SmBnsnpSC{L;JZU zOQ~F(xE*Un7$X@dd9BPjP)SUCVF_?&wbg=|r28yDFR%6^nW$gqN!;++D8NhhOlb zD&)>eBF#0-#ydZ>luh4I-jrSJ7E4lnKF3h|VTIv4hFdMb#Zpf8CQ^;AX?=srNXn%q&pr5qp;~H|^Gy}FX(fCS zoSgdBYJ9S~Hsri)&mPtO(c#PCYbt-DuLB(Hud2XisYOCe`25Eul;tXrMc8Dw)a(qg zxG<^tvBpomeH}WHs~%+ZK3Z{}8m}ytqAJ?Q7s#oT+N<3L>PhNH51HrYB2@cS_0ZB* z<@bnn*x2&Sa0gS{{{XK<)y*(+T@GgU-YHRt1E&YMu3EH|k?m2bD6=8sE0nGW9$gPX zO8Skrv1u|@3%6VfQKxdJh*8|q)AYH^E2^>;J$hG7DiXcUD)_is&r{O;H5~q2f9Vmb zW0KBBKLN@7E7XE^J~JICbGEsHzHma0r;$>=c)XA~2Mm1;Hw&v8!m3Z0p36qH^3~X7yPDFT z8Wy`Ut&XX7k+G(y*=|*!P^5IlZllYw=VB%8VQHP#mm{=s%RBDQKt<#O>FJ;3D@57z zHQ^N6kM;e2CCjZM#_T@Or1jheIrSfp0<}?19Pq|fq}8@C?exiPuL#ByJQ0DKj$1R) zo-Q@1`Ihf~+jDT5ZH%8gj68#ogWJ^Ci=1YEQm1JAO!c^LBUzPKI*QRpYw+{IdUuEP zSDM;cCcT-lD?1Zk3s)67%I%-2QNmNA{nT{Vkx2}ZZi!PPAdZ;#u1BHRnq&0f;K_1h zFO$h*ka12dQ(9RXk!p!;5Rx4{Z7YnC!TMIJYq5v3lY5mWx_hWv(l{eX#z}OU>nB^rO@i+1(SGJ9u{o{`Qwanx1SnG_v z%bRoA{{UyA%^SRlCIY-?lb>4A!<5NQOF_D6IIf63I_6oMG9ZP2h;{l?j;vKC zk{fy*C3OY2sm56Kr+qhNN?MJzV$IE%xBcWdOE?G9r8^L%aJakk zux=1J=&z1N0!gBDlEG}>V>r7V7;p_zvP3w_{zhrLWVuC{9Onw-x8q9YjyFV*YH#PW z$TuK8A}Iap69JgcPcR~&8S+-Vzy!c zp-x$`l4!cMgrL-}bK0JrV{txXS40F8{{Ysgc4qNyBGl4cPCwD@ysJx8H7q>ubE1f%+E$(i0O+{_tfWkW<9-Zm#^dZ_R?CKs14Lt;J zD4OsnJ4YK&y>6hRxyt7$ZLy*D%UR{Mc_R*2gYBBy>9ZS2T$fz%=KRfM0CpdAbUC7> zC%P3pM@DnHrJ=F8hS_|!bNAnNdH$8pIJmnVFqbm9r=n?ECFpETy@37TVUB*aq-iE) zI@5`tsd#Tqxtc_?x>)uSTd3#Klcm_w7>93o=C2`(Po6c8cyHndp*iCfi&i>f`H?Jc zmlsHk#xQ!EXA~hl5zsG(pi39Ij_x~_jBfJ%y-jQE^u5eIwzjd4aMvh}BvSbcCj~&p zeX0{^yC(H7*lE&7X&v;lCh&SYa6R$bt})W)a+<#>)#;}8^5WXHPP{{Wlr zkwV-gv@Uq#!4|s4nr;#}l@b>v_gm)@D)HrV0 zt?6p4&P;WqJHuv0<7;b)p^hs;sF=%b82ktL_orGdt0MA>u!NkdYR-Si#k$0**&e+s zE?q2o-qP}NW8l!{oj}|7zlhZRUz>7gkBk|yVW(PYu%K))aB|1iw1qg+N1a<0Qxhj{ zgpy4Js3C-^$iJwkbo4|<6N~1S#;=DEiH7jrn?b=o+;V<`swm^eyR9`kdt1AED45@D z+FU*QyPB`z+h;?fXAJ~`PKV4wV~S#&V1K7T}Mp2u@WiWk;;)+xVWR;p^K+l z%ufoFVx#GbY76yzj3Et_`Y-D{VH^Z=?He4Jgz_b z-K)#Zvk1|o;-9>Fd^wd)t#wjM-G6c4XdVcYO14`kZ$9N$FDW}ouYkqYtB9X8-Otsq zR4Cyoa#L1ELE)V|Tk1evBfYv0m2uPG+*UN>uBSywt7tb~xVEvpme5SHmOF=Bf!K3O zri`jfnOvs@)5HQJ6(oLHs8X?R7bTBVoJSR$pE?Hl0QIJ)(3elCGYhG=PXx?L{IV`S z?_Mgd`V;Q08Lgw-TAdazQ}8k+8x9sZnJQ5rZI^AAx~QYuKw zjYkJ#lf0VXY`0s3A&iZsINgfpl%B_QVHc=s*EcsYh%VP`$^lkAN2MuAv^YUU-5L65 zc%+fe-6nw0E`wD5K z#M6{)$^1vCrlDw&Dgn5B+*7MHG>M@~P?K!HlFcE9HqtT9H!B`1oyNyfcVgS>lj+g| zV+|aNe(xjkrk#Z-a}rwIMRhjl=6FjSDb4^iEyXD4LpAO4NXs-LMBb{rLKUURF^VbgKUaU;5wgbuVU?Uo#HjM zj24)*-dY2L)7G(PO4xG~9z^9qZUTeNAS8+zRv^I+ImIw8>6YJZkgp0O8EUA($cays zwIm2tEg;Ud5siKHpAw)FL$FBw*wL?@m$W2+hpSGR*f&HRMe2A?TwS zH9Xg4+RH<%@aKpiyp^q9-E9<|ffw}Ry6H_zLyEO0A9(4ktu6F>DV{PHB)MWc*6Ioe zBfaxd|7pIJHruZV*mg<^sOg|Z)2JmNUd3)bOTC=@@$oq5=pGwjiVbO z@Y^+@Td3wD7Ja3;+(&bYX|xR~Xj;9!wzj-~va>vFqlQzQ_o{Pgkg4-6NEuT0StoHH z?#Aqs!1k=&-lmJ)Rx>`yG`A(<+*oucxipP0p>pyojUdM&Z`&#j-!?(^`c>V;%Sn&i zZ&{`C*vAkXm<|D_xRT{IH6Y*EunSvwk!DPp4=3wdwz*6aytOd&9}zyAb1ZT!UczWiac`;pp7KK_pg3jDM()*gn@-Hn5R;EI zcQrg$;XOB8v1r=bJ^1-sXs*j!RC&s6=_BWV7xE@y!-VT1r%H5Qzj={=XDeR`Eb^j0PbAiIh27EUQLhzF)jFN`hD_?HU0HHI`hYn9 z0DHA|!D*@T*yjZ=b^icfg&kK+xlL>B_mV7-vD+H20x&uOPnTP>rw@vQ!}}^~&##%Z zZr0MlCgLbp1)Z4Xy?>zoUF%EP9%N}cm3eo4i<%yJ57tjXwq7+H*)hdg+VOGy?i}%x0=J}InGUB+uYtUTNmSbr?nC&f3**haClMaig#@h%9Ai|;&Um1g3QctTW(1; zlWgdXk~MU14DAhZadfG;93gDg#+!SYPY{-f#?@fE((hR$i_F{1ZoG!9cJRI{`^*8BeOLK7r<&&8fLwc{wgP~e&riB#Z z{D}0}BU{MiyPPCFMtLOtJ5q|*QO;UuU)S#-xmC2ZaM6L5J%`e?sMV~DsKuvw7_qdl zWJen0%}pxG^7dGzM)HBrFUTgYF>*4TqXbga;tO>pyp5Jzf`6Sv)t!wX z?yiaj9tHmZMs~(I2cb1P7rB2))h+B4y!OiS6UjV*R|zQYXHpJYCP(4{ZGHo_u!Z`D zPASc&uu_YTgm#v#XKs%v`&4eZ+uT&xrth(8?$SiFTj*GM2dcjepW{PaK}lZ5X_Zxe zR@|r7q|qU=%Ntvb(fs`Z0)Q)+A^Rf?5h|RkgN%C61OVPz+qAMuE*J7@l_IPyoYvQ$ zySTRn$T&FUW|9W1Otv>x%jPVk#!m|MTa(P0C7?;AWVlJNhJ!$TjU-Oorp^X%++Ei4Jr1r8_XE8E!{!eHth#sUnVwd!Or4v?;~1 z3ldjT7Z9#)9fr}7A-IY}w+ik7@_HX^9e`Uz?r*_N^xdXHIDp?{4l*iL*u#BPawQ132P~>0zk**DP7;4+e%TO{+^L?j!)6 zqMDYLCQ!VtQFv|dpgd zO0B_0^2ukBhASRsbU34aJIlR3J6m~fBP}AF?rh_aTHRG?9Ik|9?v4k>+Eg0MQX!O( zp_zVM16jI~PeWBkEm@<*HLnlqz8;cUZ6yUq&9SqLSFeH0>Qk2Im5#}&Radd29;x=L zC|)h0_JYGb4mq#1rHWNxq>s<1f^=%iGbvtbf4@j>!H;pkW7F}XRWGADX<E@W}H7jtRX#iEd_dJcqFpE){m(E2!0jv}3;j=M&_`$m@>`-8~f zi59!5ROW(b&*Jd@&a;n2vDJ8PA2hBm8_PVE>5s(lKDE^arOyu=D?3|KZx?H@*=TZ! z*+Xr_2nI6P;{(uti8Sg>LM^jAy9GQ&c=GMk^Y}G;&2f^>dyFVK+3J1ko)sNU8$Pah zI#?^zgnnnBcuohmg@)EuRlz%OKNH{RI@e@lW90K1%2L07>-uGzUlPRkjb&~;(HkE! zMs5pt`k(SDqfuW&8o6ZEHBx$CW&M9$&3n1+?k$|i6f&qFk?WsY(az}esZ*Ufu2Y%N zF7+=ILvyBDTF-2#+hmbYuNmqOeAmln8RCUGLLU+O23f+q_=wi1?;X!p@I8f(hwY%Y z2+NK`kD0UHzAANPQe4w_KSQTRG^5J}V@6b(JEj1jKwrO!p)KYyDC{n6zXM34F366OOxj z&^-}zNPs=uHqrr;$t3V8$5uq$mCapN-rg-vBOC&Fln*tBKz|C+#azl0ZY3+<5l?G3 zlN5P?5w~wNbK7C69Lns^y}36qnK87JoQlp)=J1a$g5GKOHriwtlFG7)vxOZ%?V3(H zTxvZ>?RNummvY zXG23pMU|Kxgb;9h9-@|oSh)a8CrL)#uY!2V285N#ma4Dj=$99BUfRle^wV>Fx>?u(5Jcb)Vd+|O^DCJ-+1V6zX)hN}{>%sx zH#wAn$v&B=lWADe2{+7O9X@MtBwUnCk-xQ*Z=`QZA zXH{o16(D+MmCNN!=(OEsZs(0;2@ISntaWMXV<=f2%%<|z*<`uIi?b@MGI{Cv*HdoV zoGC{79IgGGt5{6S30b5hmO1$cu&#MUTIXbD?vaTKPY{hZ5J~&T(zCI>(WxJnWO4hp z3OVPD(&ZtuLr=GkJB5+m6p_1;!1t?B>Srn4Sb$jxWowpWz~PANPnP26vatfmsLvtu zC22ljBMsh*wksv6X7b_&zH3ONS^TgAI*cxH&J7I?H{6cwmWe{SW*sxo52tEPjw@3> z;(LVHj6)I2V1ASqqG>^XZj2Vw5+mRp-33yW^fPefyEV0V^vi2OYhvT~;1@Z|etz|= zlw!0njHf4g8FSuhlHA79F_svH7h%9f1HD6=W{L?LmiiIBu#W6XP{_OfVS`#KPDi*HRivVb_omFO%Z zb*`Ora3x^l1EF((PkPBkvv(wd+e?<};y6ldF8g=({OD0_gPK~GPMH;+sA73p$`37= z4E5_(%V%Oa?;E1KyPJawvn*l%0Cwy!ez>4IT+(d<@>tEq;_dSC+qY*Tui49XW$mDy zk%M^~u2~Pt{{Sf?oQlnxXjYp@wA6EHdl#FPAsIUydeuR+$*As+74b)gH5~&}o@-st z3byUI>MPm7W>TcoUdM@#%Oi-5($uM9_WIg8NQESaf}~*Q74^7!@WiX5<8c(~;3do4 z*MR}I5pu*0yn+2|wkt~>Y~^Y595%aV9_iUe=g(^Mu@tWKKBEU-&`InwLW=g@>105` zOJSEh8j28{Wpc5TrBV(o^*;`kb^t<5xXu?R2iF}3)Yr2ePlm?cI^R$0=5rn|&~-bV zQSPT#kaUa#!iwjuLQ<8_Yb(rf^ysKZPUcpdqfKY1B+t269)^lk93^&l;xW;!NyQ$S zq9Qy%JdLm}TWCC2Y4KROH7ypUEmqS`&>($DPm>nlB-(v@eQP?Bg&6a_5z~gPUmqn$ zchJ@F=8>p)vPiG2S?%Ud#P zPzLzd8-VSB#eAewsP50VsVLHqEK3^wovqfYqfdmh#h*8j0rsU*3A-WDsU;TVUe+|I zZ{dwr`^ve70RCC|n{8C7rig`UMq6lc(OoQ4O&PcI7<{{!ebeh)+a30AQR#FYm12`_ z(ej+&=A@>EN{<}gS#9om9PoYfM^Ltd#oWsqSxQulMj6*1N$lAk->23)eFoj7wvyX!2?kn{$WAAhWn&nSZ&~Q#Vo0oc})HXBP z8wQ3sdXO`Z#!{{h*5#NfCWpH)rwHFx(L~Pw^0$0uRIQE6w4Mirs_{3Hs=`tp48H~ zoUE=^nl{}eY`81IIOJlLhM8s!GgZHcO<}U-0mG>~SW{7Khd9S_x?Ebpc2-#a;VM!= z-ivFf+ihq{k;6QqNFxYyz)qMHa_St-Zc9Cc*WbLiS(Vgv2RStP8zUyDxD z?xG^#vV;69$*WLI%2gz`GA^`(Z*a?PHN!?*>meInoF5m_jgP)+P_NgNutGPRr z^-XW>>ljOLpbW^}$u*rxscdUblY=0Htn9mpQQtM1)VAz(J{;0?b)W64BTqhf&Tzj< z>Z3_+an`LXSjPC5c<{*`t+lwr0$3;+V^N9T$kzuO>dt>bu$Fg`epD+S`8S*e9r-oO z7`ArKX(5o>L{?{5(stN(l0H@U`q9chat#jFJAGO^f?6>fWH#N^a8_YbSBpoY_)}7A zsOL8rl&2t()K_dIqnmNmztXM*}@;3YU{QX?v)!H0wDnOQfwd zrvf;@?docIG`M?Kor-OtoB0;b)OlAda_)Kx6kB#fb$NAWhOMBcou0u}aV*Gb-V*J!>>; zB9*h+T3+vw3q}db{{VRZ0EH#KrL@}6(z&|QuCWYO?j6oa2d8{~b#ImIXB9QMo2L&y zQ3xU50x=IO3}?_{rAJdqJ4BnrHv6twgNBhn1rYqJ%@WayoUV-E^2NFC0l)!MZ&Ut7 zP0cJ?(l3!`Pu^t#!7MT=qU`k|ak|jzZ8emZ@=0W7cMI~bMLw0)8mXKzpv^e5e`51} znkb?}$tUl6{#9|bjHKKd%xjWCbEw8F5HxY%J4S~U%~IUj9h9%jsb11{f_Y<=Lk?GI zJgN8hq@9auk|(j!FRs>Gq`1w%kO3Jyk&d*|Y>7@W(5tD* zD&DeREOE;c;OtX_=uf>&w<--aq1Neo&85zh5=`JYO{GuqS5zjPbvPx-xT>D65_a@e{#%w}!PTF09~bZH_@z9OAu91}WjCuE&*_VWEqP z?u>mSQntNBjaV%0^6h2G&!#Kuu-KSZO52|ki^5c)Cfd;wiwjhg`J@&-Nj1~S9;R;* zQLl84<~U&k%R$gml1Qp@l=o)eha|amF~<88^pZSCb}v4o+VhyGWU1>zpluwtLRquaET+rOTirdc>OC$MKeq;VN#Q` z)fu*L1(|rRZc!dSsgt|#^!nF4rx@nr zee2zHSe@~H-*ZESUV z<@|P9Hd_r+DwotZ|$TZL>xBdB&iC$&Ol zw7Qb5jmx9UwUhz(u|=43MQB!x?TpUghmVrLWOeUC2eG3I$9rt*+M6jLjf?AAb6m%j zEs*#ox}M+2mUWSk?Gj@fhxpZVT?>&WWM0bQ}lH1G<56M)7TDO_4Z;Z}7)Z6o8sRz^0(~gXZE27nhk*M4OD?0g~ zEK(EG&>FQKW=oeMH}v>*4Gwt&Mzg?ICDV*n(T^#~QgNuXD9`eSRsqqvv&06lt^uO-pjGPtek^VI?otdQQ zsUt!yQ#YPvpxG?V&<+WsonJy-Cf25N&ny?yE%l*&=jJ`v+*WP-+M6i*s>L$p^hFN&;);m+nO$WiDko^VN>l&0kobc?G9=8^7WYl$(o zIQL)=tq@6W6XtsxJ{;7NIi40U+Q+z+&m^B?Tho=aIqK7Fsi@rA&3NHiq?T63TyiTZ zyStiGPAG=i5av)lQ4}OZf}<^NA#qTcey^DXt%Jl z!s#4J!{(5T{VAx5Qnuxb3%1c_nPxi~zFnYaAk{`(q)IAit#Nbq`;RhM$yDj+1xuCL zQI9GTJBjV?T6<{<54yQM1su%>B72(t8`iDu5t?|JoK1b@0M_xR_JN?h6pDVHs}t1ih46Y~}vR5s01M_b^C1lo6+a?=8G zBu*6i=DKLc>gN_FR+Wj?=Eq9Cb0yNvcyM+B@{ISVbqA>&u5r;7C5jzeR5QFRZGg;# zHa|LtX|qVT2-w5FwR?H7XvCtEg3Zb1vwAaVG}fk)1O1q$J4xh_46}h$iF=us7Entj z`^GCFUBnI#wJRE_S!i@NH}L4z_ONNNz1fr&n6UYYJwBDw2`DWN8noS3+cb6QG;8fV zE%wC^fJa;t#xq?GO2-PNE1x#}Oz^}S9=rXgr7LY&OQz9`*0@YtqftAH7lDo}g_SS!jGz@Uz0o(pnH&Tulb3Ai3Pu?Z)hV)9Y6mwJSZ1D$<1L z^F!i0?Go3^CAKpw3}ZM$>7L|!cdtg8K2sN3{=bj)=4M_ER()Kwp`)}@nEK$KQ&aCb zc^$N#9-RwES#>uqbvQJwJI}e1NCO$@51<3n`PEge8ZqXRJj!@U;_C8KMehvwD&JPu zV(})aa?Xwec?f=1IO8I|4>iptR#WXkS<=aKE7O46IQtj4Nx*sLzDQu*~eOh#iNN}f)B zo|L(@x+|49^0Q(cKI30TyPoDK*?`1gamRYm#njZx+A3Ncwe&4-dpa;6uK;^={cD;^ z%&A()FW2vsW#wlF$sZ^ZhWwC{-z?0iG+%ky}5TudO&wP4UJk{0F-AT2hF@>;D z1EU`?c_WI+9SxP^p6=K&R!f+lnJD$t9xN ztE)U?A@TE(MUb{+mk^7sJW0Gg$ZRt1!*S?nwwD@qb~3~*0+}L{0QEw0N3BVE8oCNW zEyAYZ6QcQmH{B=Fw1(z2U6HnRiDa~UXIwG)caKkc-OS@uEbd@En=FzlFmibZwPMz% zYLc;P;qNs?Se7upQV*C8pW{TEWJM@yWFpTD&hCoD9l2HOk6OoLd)*eTZyAb_7S_%N zMmE#A4P6%_yYi(iaEmcK9Grvr)n8k(EbU}K&_Q!@5v)NHWGPR)53!-!Vcn&3R_@2n z(muc^RFVK3nFewVXyNYI$yBtiW?Oxobnz9;Asn`2l}BEBR7ERlYTLeqm)6!->lsLb zTqj0|V|UaJwAxJyE=lS-!pWf2OF4?&A3Jb)QSNQe&P_$#&00wv8d%%gStr`= z9$i9EStHH@eQRD_F3dTvFLh{hSGLn#&VGD8;DqNR=sD|I+}<`w*tpa5yBkD@?UQ}6 z7jXwWD_0vW^)lyjmf9FPn%<<$(n;nzbMp>B_Z5q|v{LA3X}1b zL^3FSfEW~pg~;_rlfOJn~N*>uS>RDn2@N2 z2>AwZezi3zSgJK{W5|EA?7WqZS1kLP_i>M>de38jiILb#xdp;D*h>&N>U&VM2_kzf zPCI#w7a2(0<0HK^(KCZXBT1jx~>OmQ;?X{Ap>-B&Ex1o6~q+3+?xD-86B! zf{LR7Yj0tFPG7A!=+0kLwVzA4c&3lch>!KBf&uodTpV4FxJ^x65n5lvbhhkgHs&_n zv-p_%3bX?VRKea@X*?|j!ORFBI(w$0I*osxH zxq)>MiK8<}xhD*uagXa*G||$X$u-DrFQbZUDH#?uz{wnr^yO!;LA9eRTTgMK&kXa# zrCD;Lc1r> zmr8Kh_3d9_g|8aa)9QRiE`;#)T3a1ohhAvZd5A)&ATbJ*Q-wHA32UtlhOYG zUWT5dbn?I~?QLG}@zjW-MdY7T=sH$7%ym31K53Jc<#n-@(xkJU2#mWC33(JfBC*(K9ug;*GwaLk9MJ!{B%p7M(5 zYr(1(orXY_u&cA7&uT6w0{!NfHKIJ%6CmJz?RmlNnrxqXw;|M;j}7m=Cd+`G20(tx2gw#*Hs$CR6Zo33Sau)+Y*b zrA&wA1Rlrfnusf=jVik|$@L8{Q#8hr7CUe5vv`Fx`mXH+eGoe@OhY1 zo|);ImjdHeGgIs~mKs~yUow57;Wr5xZ~^QyR-ls9#nf_@(T!|mh8MRsa!Oa`GpWjt zY*ZWS_BAbaTV}Cuv%+kr0Q|}Pc=h~iMI~(vWlBx!v6**xZ*3)ejnXq?Yi{L8{*{|| z)Y=f`*tMZv$!dJm2-(7cAmmX8Vrs*fyW&>4D<9h6ToupB=~Yu=DJv8f-VEVzB^J#qPxT8y8v?7`IMf%^)@Lo+=$5p6NuFY-{Bbt z>+e--6T7hM4>sy!5-D?xjzARE*%RfC;$MlZ<@w=oNCyCO^~DXe#*%A8U&D8aZ)Y^} zzU;U9ykWm8x@|34&t5J~BvAWAVtJ%kN~4^db?3E4jojHMWQs=EUfqQeuwvXWIO3<4 z?9%pEE#GM72Oe89!`PF^;)zpe&J|-LF&oLa=OKpyfaCF~>S)%6#lu|R$O8z;QMYpO zfsb6)t+ry6Vk28#*+aBI%#L_C$Oe*451K8J&1-iCE?O`ckjG);>Fr6ThNPvTFNpN{ zEwof+nVr<-gAcvMIi{jzO{q%9p6Vuhz|ctohGUQrV4CG_=cNV8M`>?!YpC0(a?O?aFHqa=>9kT3wY00OtlxeiC*)_CV-B>7PBfn}u-H5HX z={G{|IMs?1h0orqi?bA$GO@LH7NK>gKCh`y3P-mwqx_R&qFdCVdmY2U_N8r% z7v?Rlo!)3bIn;?$Dx?fcQF zdsa?d&g|J!7Q9{2Asc}m z1}Vih^%UpIv^sq@=S{N8Sh7PGmcU4PXE@`ZO6ZGGS{##%r@75pYC6U4>qsS*Ne+JT zIB*XqKdvi3X(yqxr3kCC%+@zC+ccVlo>`4>vAE@rYKIwa%8aAXAcjcdk8vP+@ zBcVf}Y3BOzbE;eb$9Vgr(0&HKQ!&SVVxyNs?Q)#0VCbkV`kGpWyz(n*7Vzy(hi?EM zr@eU+)!g^tWuq%bu*()Mj>Lb*EIn#XFJdbzb8k74-e}dK+`*fvBw%CH)`hr9-4k5i z&3fp!B$*Ti+&_yIPEJIqDC|S$Ngom&+3WJ-=~7jT2Gyj6S|G?{0rzdlVNSxjEm~My zTtc@OL12V}%*2vz>x0`sf&yflH>^hJ0n#$K_Pc@^VvG#+Y6}7PoBr~1NUyyj>wQrL{ zoiA&vF>U0U<63DgEtMrxxGEf|^{lV1hefR&OBeIq%{s$9xB9jFqC8+8q|=k=D5RC{ zby|I{ohF<1R1p*_wZwh?xUQJRSm3ErT*XV9DXphiEZ1eW+y`@oPr{2dQj%)y)R|+7 z&NaF!%Z!YU4Lce0JLqL4{HwGjil{>l}AL|rlfjud5s};=1RWS%OUZRh*v6-L=>|^XdI9vVHzz&MK~qp#A=i^xEzlMaw8t3v*QxAqDr}P+d79LU zSZ`*Pp@!iN%(48~A8>jD+wi4gu8bXDOOH+RP|1n*?vSzPcc2uUO=CV|SHk}QYShWI ziv@rTT=W#wZf843R$NFxzH4IW;U61;&UvNFsJXPc)!O}z`p!!^)+B-f_i{MJbt^d7 z;g#bqWN|wEvDB|sEQpN(3k*oRR1Z^*^@Nq(k=;$&KJd+FN58PQX~QcrDuWnq6!#{H zMZ|Hb89NjBXHEF&D;c=lgTy*j)7-~6D3V;9h@;6rN*r1$okeRBKAs`aW7F=U zl1s~p0aac*@yFv&lR(9)b++|*S2ZkU(fWsVzV7$xM(NzZKcsd2cI<#sVHA(^v% zWPpYnN7MXxtlE7Im9}GHc5R}03cB;0{&XvYRyw%6K_Y2(_9jUtX2{`kyL$@g=X|y~ zJ(B0%3rjSWgS~iXs*K9e)2pF$WJ-{01B3@TxWHt-Qgq# z0-|hz`1{iy<@HeunRbU@xKoaUoZ^s~4U{51N;jQaD1i>m-Lc33(~7eudZS_;Rj1W_ z%kow=07V$}J@MYQ=1uBg={UzhJZU^IUO;U(e)UOiFuCl1ofQ(kfkN#&jkN*`g+R>q z`NV@CcxIs{T%F0J&|%akSuJkT0W;-e>QzT-wN1T<_T0KOE|aCtr|Q3Gv?(Y;KxY`I z7&yo$RZcc#j}$bMUfRbrYS$iC@0Xr`rBzF^Xy};*y4>kcERvw#<^JzH<2~wEx>T(_ zjOnMmHpciQljN0V?m55$p|`ndC7_|fi}$Ei+Za%|skv4lHx2+>leq54!t@!XailtM zu}Jg9x;{3k>(6ugilreq-*acf`fjEg7$>z~DrOAf`e4@d`MVsooF7vDkKsKK$^D|% zHm3O;;%LA-dE&QGY~+-bNUwDF)iq{F*vA|kO44*KpX*sl9L~o~Dkw(eekPJFHg&at zhlzk^1E67=nv0i2X-D7G#YmRmksNLq098*Ndiz!k>DYDdm|}q?i*Ps|U?=#o%_~^~ zXr*gA+up(_4A85M=WheAdKTkrO$(Zqu?5M5Ljnar4spga)7Gj@bRsZoa^;oh+9DIV zFA8$;XQ0pcRmW2~#iOA~w0F~_wvKj;NWUnPIb8Ey9__4-HmWIIqnVz_z`+d4xcRaU zbDHPmXzliitDBQp+3C7!T)mphy)Z!?4Q%408=SGLDNe>i>P;@6J-7ler2ha9eDth& z^w8<#v$%rZ=TY*^5jzl=CvZ3ex%8y(AXb51(QdTK9!K7i@2c40 zr^#)6LlNAH+8*-M=BU&jgc9B9nsg&izITu!k^%X+^rG=vF{ny<#OAfjZ6i?Kai+~C z_0ITq;Hm!r3d(6cQP)MzI!LFY!wlDE)9kM40y8oP3fbeFdwN!BYhziqmCMHN)@$g} zHIz=wRr0?nAJUtOC0@hy%}UPOPz@p!QSzO=j(XKbDLa!Q<^;zzZ;(h_DynFD1& z7$ZIKYN{n9VTa~~taDcuEp?^4rGfL4C8SVQoOT)S^satg4&Hl9awshtBMUswj>zkQ z^#+rPe94w(yq%#inHUw%w;#%zZq_A5T#__~=Gd3UdOD~B^QCPE)P@ith@?o(3u6Sg zLFq~{vK_~Ub2t{v(MUKdr?1p>BNS?wo~YZ>^>J+=j^ZK-Jhm_w@UEy-=*}v*d+Loz zEug#H>nE75K2oN&YUetWj)qpZYXl1#$2Q4G%dfYsWjLGCn{2USEiK?yE``q=w^8+^ zqPGc7O_@5yjJC0xfa@YT?budwjgE>_Qqa$i6rK|!L|mX7LF-wjZ0wkjSV%XqyjawI z*jpza*`n7Jq>;7ZOQwQpZ6--JH_VJcz&-h`sJm`*;$wYLcU8WE`6RSPwriO1!6rO7 z_BE{9k};f_S}m7!b*0VL|uV=Ug) zzq93zrzf@NlCjTPT(zW;-4$OuZ_0>c(!Q&3ACuEV!A*8*25>dQMk& zHH3LpjSUhN)2(HV9mJ9@7^hVp-t}>IMs+>rV?}gq@8oM>>gBe=xye4{_Nlpiw?^cJ zY?>+V-KJMf&Zx%){{Xw1u4@@`scaWIyGsycwuae0_SEn7^`$9x4`*%2WZi#nFfLmo z=MC2sxtkh_i!vhc%(H3{!3(@!ya7Px9{8dy>}ckaxi+tD{{RT%i)l=^GD|PW64<4F051a$I^H7L$6Hy2@W%p;g_r8M+@(D`+(5 zsm)S6u+Z%ccJAV6!pS0n0UbtbvyvwdGuY%jU8&wn44Q4a6i2|4U+*7b+PP~~wufFG zpW0>*hit6xH3+8FWr#%z{^<(v*!S&N!6{2quB_Z+Xpnf7?x)mcx=-A|za%iI!iC2a zsy%ca74GY~Q%|}t9B{E@iQF+K_$=clw6}9-Lj15@I2zK5m(g4 zq~zDSN2|eiXEZIRK&o3HD)l_|K7y2$m_k=tnXf#SmrzYE*r_Hg@4Q|g+ zw`*wGBHF(@=O2*u&N!{TqG;y!Qj5DQe`8)oC%a3M3$I5VvB1x2hZ$~HwU5GcI<58e zw@oDRlnMef0B~?P8LVY1op7d-(Am*7sM67(@&Lyw&(k>T{Hmc*C1Wa-+jk#5mDTT; zKbF(V0bt{*kLgNU>NvF>42hzcN~p`zcjsAH)iiJDo*%in)6uR`q8rNvJmhg%@OX7cTa?Ysn;SGseFzIQmy!$n)*ZEe>Nr)T4t?yYiuFB#?$k&|yiz$*f^h zQ>vvK#TNDZeOhVSNc^#Ek(X5m7=FC^nkqRxf{!9rdz_)vT3Gg+Gn171cNnf$CehlU zmcxQPk|Id$AMmq-!hRj;6io5m0iDexWZ@q;+(jCS=$B7Bu0G7)bCN&W;+oaiQ+8KG z-X*oOeL4|f+himb1b&~LYc!J3&Wz;csga>UaF;UfP{tU~n2pe>$4`1uX0Rl< z;z$d|GOM#WJ%7T7At-67)?ewe%MHEuoRP+4g;G=?4*hFa7S++prjxTdt#W&~5rkJe zj3jO#?i-J4%2In9!V!AdhSKG)&ywiz+`iS?7mSnt0N1HSwPUH%Y^Sl4?KZQEXya5_ z7H<7R0zSAujcpXs%~DEeqpw~t)r5DE;w5|o5#w*Dr8jq|=|QOIXvGGFaTpMJ`Hgoe z2h)N+wOp}eIn+jV+?r^)jqM?nI3@B12w~e9r0k3L+SKaw>!EuCG!icC4Z(V;$2k?% z7j<-SREI0ng6~YSl$luxxFD;QJwfh8MBtvLlhv&jg7>Uy zKBIG_MF_uDdtrq)5sm=q$nC{wotCCH=YHtTb{I7w1W>ed+c(U1KGw5#IVY+sYL@R~XwiSi!jZEKc@+~@(9)c_oywjbp3c+Ej%Idna8n#8`qWcNZfRQY zV^aRk499(F;#kf%E_gp*O3}q`jAJ=$!%&hmx50{7l%1?&>Yw61l-gDzDpxXfyGu>+ zl!Q9xklE|)pIV7K9WZUJP3s*;Pf$Fvv9z3H7&UQl=yOUl>TzB*@dd+QwVF&)-CG|o zKZ!NVTCLwH-G-vA%=bCyE(CIUfsCUk8_scD=VgnKhPa7JHXA(Sx4i+ede-U)Qd?#$ zRQ!d2!e3D2EYJZ>MRF^{;>8mxaA-%A;GR}n=coHv9{{UTI zD^BKI%1X#>hM5MmT$O2KOpW;arkaD8I-boK_nJi3GJg6`mmj)-j4BUY`qWAdXw4y2 zOJaQmwDr@ZpHP-)=2BKT*F5?FJxyE^waBX{dn0bsRFSmv6^!8~azHQgu|Amgt)n-~ zWOK?2_hdF6W}9;FtH5!}qcOmuJpPrP^*1^zD;t|VK1=f?aLN}e zxaX2T&lReRwajHC-G#8ZnTttzIr7py%BtTpw?Gf4tt;G8idz-0ZK1ws<9)1l?q<$5 z=RN6lDLWQnv}sUEWZvqbReEQPXY!?By^Pz2f+r|b4W2>w?L#RV_qTG-V{aTWM7~Od z1P(~|t8VET#cIm-sG&v0#8J1Iz{fj%@luL?kjwo(`WRA6If+R!85r=soci^kBGYyj z(^!{JxMZ3vmQVvS_w^r26R=93GAE8xer0eb?IL|Jd1buSCvuiJ9)ut1Q=sptk?J?L%(1ge z9_|zal26vDNupgivwujE(kPZIxLDpw*;SBZr#Y>uMmq>Pykz&YEV)y02$=cCHp zY|e_w-s4H#E$i(d61;FR$Q8U>k~t-4%S2#m`cK)XQEzt~$U%`@4p-DyJmJc9)2Yq5 zp0fEiqzJs%B;>Pn`U;h?rL{U6Ygre=@LW#YqW};GT%G`}lzr2k&)>09&dxig2qb7G zA1MT215%(=r()dX+v-+USGMu_F&qa2?*a!W@~Udi%$2NLMICGGbKi+A0tLa`PZ%{a zwe&8ZH!r+kt&Fiwk|UNZ*^NNY%uiG3DpNOaL%i3uFSBVT3yD;_Ceee@{f~{{Si#M#)yYl(NtU4BoO7(MFJ>dd*dvM50{)}GQl4Ds!3FFYY>m9u8_ih~$+NCzzdB4(HyfMT%2au|vg^&Ml^b z>I3!}K3%|PlhbhQDr#(Lhf4ltE$sX4;84;rRqLE`KAzQ!XIx{eGi1GqrU+AejAskM z$Jf1P%@VO@MBFzS1dHD-&M8=8+ly4nPdtcJ?OpD00PY9%rjVN^j@{&(yx3^fcPpdh z9y$@%G*~Vy(jP0yN8K9%^6uy^mdB^PH0&uZW1`ZvnB8Ps zz)P;syk~=55vZ-H$5M+<=#JtGtuF36kP=AuZc+eJLGApgZ(_4T2)iPBn^ z=hOhONDNBtf^(eU{X122Xhdo2a-l7I6G$fmXx;O0YA+3Cb8LX4DI;@-ytHgSd)D}B?CG(6Y=VGCT z?%6)IwMvQTb5W$??#?hzsaWb>Q-(MN*_iO48t3NJ?Bq=yFWOIt=d^|^cZGNjGK_8m z`Bk^f>|<|dcFTSv((SF@D7HqSR~v^VuzJxr@`{~DE0~(hx|XAb1~}tZ0Tp_;;nJp6 zE^7!Ssh+JY3L}y7&fsyLe-FlzW|l;gEEMf8!C*>!w_UHM~kmV~4 z(WH{#PYiJRlPYZr4np(PXSq3`C8nn3mZ1V^@@ezhk2Ox=yZgtAwQE_JPA{71&y+0E zn|tV&k@vBHJ^r562^yx-x+q^;OQ$Q17_^;;B$69FLG4ZG%1@i(DOhTZ*AiRGL0Cu3 zz~l^d=e;7FRkS*tF3qk4cQP`Y6;krZ4o|Sas*GDPob9JmKT(p#NsC&@79dX0Ju`!v zP1)#FFKtE8Z6T8B(tDPQCd07BQqm`zUD(l*# z+TBXF>V`QKCo7y|y%MQyhfbF=GrrKjYYDurV;N^3#aXwhs*6azk=tCXG5P*VG5f)I zC)YTpl4MFwD&p$qD=T^I;z=6eMiGxWUNKcGMOc_#PUcOkONmx%JFm2|D2x^39qT*X z-pOoBrraXj?J>mDM%ZFN`FO=lY=QG1rD+hRv(Zi})yoB{?%w<4!-*qLl@ z?Gh=3im^xm-N8G58g^?^dDGB5LnISP43N7yBknNg+#4W-HLX=xt9ZrvXr`l17&4G9W9K2d?aMT@>t*$6uOEhfsxY^;oZM zytYMPjKqAciX^V?il;tU*^bn-neT6Bf^VKLFMK4fR4_lM(v+id!lH_~mwRPA8iLEG zZCykfh3}Q?Y}jrnEJ0?`$q? z8_l_iBb_rD?nw5klTt)ejH4r(lF9GjM7Fn;9T+i?DcK!8pe}aNa)G)uJAHPy-P(Wa<-*&HN!_8-LkrY zmFzljed%)*%1@=-NpBI2fXN>y2ONq?gNZe?kVxKa;6##~JecT5uj5T>OVFAo5UjGJ z1`N9u$0PElCM1?u_Yl00BA1L3%hMkC^!igtiOMT;Y7aYA(4%XmU$wwFDo!)_*54FP zNBg>(Y-QP`pKCW3(C?BpC6!l^>w{S*tkO_e-PuVbZXAA9%DWwQxnsjjE;UG_m55@+aLMv7_!_7yvMPJAxm(9p z&!y^WA$OIW?!e?OeSIjY9##}3&aN`cV@9szF=6*}%G9K2-HD#jXY!T^+5dQKw~d%fwdab|KaEt1q@mZ+8hShXnNiRMebrW)!JT z*GDONbt*{E*+^D0azc!LHP3q;$~Rhuo@IOFU5xS;%wdL0b3%HKS|WXCLcX}vg~P^P%N)nX=IxV7GOel3U0ghAFq|}~!)RbSo-#4%?^1Ln*z7zns2g1> z-8{(JF@;3NG4wv1{wb$q@=bnt+p&u zD^8wT@}!PWAI`9`)Y#XEM6lf=0#0$i*A$AsLY?Ka-pJC-l6>kGH4DJU86V?Sai?QG z?iRLOd92GH%4FNYZnPAdX1IbaNv&YhBtavfE(ymx&;vwqZ<)AeDk?VG$TPnxtCo19MJ7VKNXYqn z{#4MJb+jv8R?PV_2rkraEKU@G*p8yKjBbqGPF7|mlzL`{WWSM`NhLc(JrAaRdQojI zQ!|1NDM$Y@5kd(4RcGG1ZEV2Q(F`=+Z!&b`TW9B3>1u>F` z@c#hyP#nnu#=Fa3?J)FNKQJBj_NnFx1FUecG4D;xK&(#_3E$Hlgq7* zTfYz+yFcAVvC0<#@z2(+%`{}Fw9Y$Gw!4nt%LgE_Ewpvxllj*>(VdaCwK`7*=<;7_ zPd(hbWX==}WMZ|2K4o*7y;TQfH&u-xVunU}hS1nNV~wYdyiqvWjuN?6OH(VMkrQYb zEEF7?lSL~Ln@gC12o=hV# z_IJ2MbYwf<1FuehDtwF;Xx_$U%C)@E$1w8WfLQRXeX1F%l9s4VOGUY!1ipk2u?(CX z9Cx9n^#xKX@|iaGVlt74#*O(KyDpqSZ#$UVKs^{pX! z9Mzq*`Fy0Nft4m_!sr9 zqdr{@Y18MsIxD?K-p(I5xonK8j;6M9NgR=H77X&dscb#|z1=9ztS zq_RbF zzNHkmTgAMDjl~X#o3U`KV=8%MF~9@69BU zP3TG_YiTEn!a@7~sVg%olg3AJ^c6{@WZ})ZJ*K;MhD%*WAtXUUtcp6Han_G$x{)dt z*y5FK-tCz}EGP*8oaMU@rEs2=-H5FnqPs~YL$f7@J$--r)YG#RmE&UPil&nC$`(VH z0fG5B$8LL?x{~HIqaIgdkx8XnpD@16HV7H~v014zr<7bgQWutbWhEW*%4>&rEbRw>FkJ-92tnRCKnI z=4UF$?hFB*$J(>8p<_z0hVJ3SvnCP}z--RrLRS=}%7~|!J%ZVYj7Y8uF;Y5p0GhsL zm4ubu$~v*U^J0+7XN(Ws?mVdUsdBk-iW{U9xVuOl_8=gi%cU#bgq4-f!p9f7OpwiP zk%Y>ugbd(w*0xDBa<=0ul&-Zzwzm-oR$~|{tQ!~|>Kj`YlWygGMhkhNfJBP#Fa)EK z@^rvItxT6=NJ-yPi)b=i%QT5_s0iAsMjP8<q~}sFsZ5#ZhzB8MpeC#-lC77V@*L%$Xay`yXnF z)NIxilu~6;DYPQk?%a`)BN32WcR^X)*K8RaYnN(_au|mP*)bFFPLXx|X6_{-V zb*ZtV(OlNL)wMxy_ApoXM`z2|XyXSRYTUH3nxvb#TG$K8V}jIdW4vw04oDp2`_!5} z2-*?lDv4S-6zpM);EunoEry4@_YEm>xJDT)tHAZ+wF0qf+1_~85d+G^@Dya@{5nu4 zp=Jk~+fJU}%xD}o4i8RnYe>z-SjtgSmDz_iz1&wS(7MQng1s;R#ZB&NtzvI7c;Ldz zwnknPnz~A>Ux@ev0fO~ z*hrgLvi09xu!Ec4|F#cFbXDUKPnDLkAK2G=>yKhwQ) zRZ99Eh8ez1N;WrF*5KPavO&WI>%r^COky!){>fWvCmG7p;5ku zkAl`W(j-$K$tH7xO13?_RnyqZ6Lln2m-{~6>Og{6Fr)?;*`H5fYFe{3k28*>2`a_} zvY}@UoCCohh6t#7>S&@?y1aQb+m(r&OdI8A=jG#ppKkOyTopSq!s?GEmmFSHjXXFx zR>n`#sx#1sOH)SMT}!()kH}|{z}>fyeR0ibswRwG2N$6ihwS8<;_7CQ;ez=pV*|^< zJvh(*09ewDZpT)9s>R(--e{qkc|yw@$B7~;7z6#`=~XE8CY?=EMh2B?E-&MNLPE1` z1U521p%u>+c6G{bF}blemaiG|36fZT>NgyERk#>NzjBMq5C-PO2 zEnV(V*=^>#2?GXDQ +#include "tjpgd.h" + + +/*-----------------------------------------------*/ +/* Zigzag-order to raster-order conversion table */ +/*-----------------------------------------------*/ + +#define ZIG(n) pgm_read_byte(&Zig[n]) + +PROGMEM static const uint8_t Zig[64] = { /* Zigzag-order to raster-order conversion table */ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + + + +/*-------------------------------------------------*/ +/* Input scale factor of Arai algorithm */ +/* (scaled up 16 bits for fixed point operations) */ +/*-------------------------------------------------*/ + +#define IPSF(n) pgm_read_word(&Ipsf[n]) + +PROGMEM static const uint16_t Ipsf[64] = { /* See also aa_idct.png */ + (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), + (uint16_t)(1.38704*8192), (uint16_t)(1.92388*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.08979*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.38268*8192), + (uint16_t)(1.30656*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.70711*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.36048*8192), + (uint16_t)(1.17588*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.38268*8192), (uint16_t)(1.17588*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.32442*8192), + (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), + (uint16_t)(0.78570*8192), (uint16_t)(1.08979*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.61732*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.21677*8192), + (uint16_t)(0.54120*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.29290*8192), (uint16_t)(0.14932*8192), + (uint16_t)(0.27590*8192), (uint16_t)(0.38268*8192), (uint16_t)(0.36048*8192), (uint16_t)(0.32442*8192), (uint16_t)(0.27590*8192), (uint16_t)(0.21678*8192), (uint16_t)(0.14932*8192), (uint16_t)(0.07612*8192) +}; + + + +/*---------------------------------------------*/ +/* Conversion table for fast clipping process */ +/*---------------------------------------------*/ + +#if JD_TBLCLIP + +#define BYTECLIP(v) pgm_read_byte(&Clip8[(uint16_t)(v) & 0x3FF]) + +PROGMEM static const uint8_t Clip8[1024] = { + /* 0..255 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + /* 256..511 */ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + /* -512..-257 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* -256..-1 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#else /* JD_TBLCLIP */ + +inline uint8_t BYTECLIP ( + int16_t val +) +{ + if (val < 0) val = 0; + else if (val > 255) val = 255; // Bodmer add else to speed up clipping + + return (uint8_t)val; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Allocate a memory block from memory pool */ +/*-----------------------------------------------------------------------*/ + +static void* alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */ + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t nd /* Number of bytes to allocate */ +) +{ + char *rp = 0; + + + nd = (nd + 3) & ~3; /* Align block size to the word boundary */ + + if (jd->sz_pool >= nd) { + jd->sz_pool -= nd; + rp = (char*)jd->pool; /* Get start of available memory pool */ + jd->pool = (void*)(rp + nd); /* Allocate requierd bytes */ + } + + return (void*)rp; /* Return allocated memory block (NULL:no memory to allocate) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create de-quantization and prescaling tables with a DQT segment */ +/*-----------------------------------------------------------------------*/ + +static int create_qt_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* data, /* Pointer to the quantizer tables */ + uint16_t ndata /* Size of input data */ +) +{ + uint16_t i; + uint8_t d, z; + int32_t *pb; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 65) return JDR_FMT1; /* Err: table size is unaligned */ + ndata -= 65; + d = *data++; /* Get table property */ + if (d & 0xF0) return JDR_FMT1; /* Err: not 8-bit resolution */ + i = d & 3; /* Get table ID */ + pb = alloc_pool(jd, 64 * sizeof (int32_t));/* Allocate a memory block for the table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->qttbl[i] = pb; /* Register the table */ + for (i = 0; i < 64; i++) { /* Load the table */ + z = ZIG(i); /* Zigzag-order to raster-order conversion */ + pb[z] = (int32_t)((uint32_t)*data++ * IPSF(z)); /* Apply scale factor of Arai algorithm to the de-quantizers */ + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create huffman code tables with a DHT segment */ +/*-----------------------------------------------------------------------*/ + +static int create_huffman_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* data, /* Pointer to the packed huffman tables */ + uint16_t ndata /* Size of input data */ +) +{ + uint16_t i, j, b, np, cls, num; + uint8_t d, *pb, *pd; + uint16_t hc, *ph; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 17) return JDR_FMT1; /* Err: wrong data size */ + ndata -= 17; + d = *data++; /* Get table number and class */ + if (d & 0xEE) return JDR_FMT1; /* Err: invalid class/number */ + cls = d >> 4; num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */ + pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->huffbits[num][cls] = pb; + for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */ + np += (pb[i] = *data++); /* Get sum of code words for each code */ + } + ph = alloc_pool(jd, (uint16_t)(np * sizeof (uint16_t)));/* Allocate a memory block for the code word table */ + if (!ph) return JDR_MEM1; /* Err: not enough memory */ + jd->huffcode[num][cls] = ph; + hc = 0; + for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */ + b = pb[i]; + while (b--) ph[j++] = hc++; + hc <<= 1; + } + + if (ndata < np) return JDR_FMT1; /* Err: wrong data size */ + ndata -= np; + pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */ + if (!pd) return JDR_MEM1; /* Err: not enough memory */ + jd->huffdata[num][cls] = pd; + for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code ward */ + d = *data++; + if (!cls && d > 11) return JDR_FMT1; + *pd++ = d; + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract N bits from input stream */ +/*-----------------------------------------------------------------------*/ + +static int bitext ( /* >=0: extracted data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + int nbit /* Number of bits to extract (1 to 11) */ +) +{ + uint8_t msk, s, *dp; + uint16_t dc, v, f; + + + msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */ + s = *dp; v = f = 0; + do { + if (!msk) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int16_t)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (f) { /* In flag sequence? */ + f = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int16_t)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = s = 0xFF; /* The flag is a data 0xFF */ + } else { + s = *dp; /* Get next data byte */ + if (s == 0xFF) { /* Is start of flag sequence? */ + f = 1; continue; /* Enter flag sequence */ + } + } + msk = 0x80; /* Read from MSB */ + } + v <<= 1; /* Get a bit */ + if (s & msk) v++; + msk >>= 1; + nbit--; + } while (nbit); + jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp; + + return (int)v; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract a huffman decoded data from input stream */ +/*-----------------------------------------------------------------------*/ + +static int16_t huffext ( /* >=0: decoded data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* hbits, /* Pointer to the bit distribution table */ + const uint16_t* hcode, /* Pointer to the code word table */ + const uint8_t* hdata /* Pointer to the data table */ +) +{ + uint8_t msk, s, *dp; + uint16_t dc, v, f, bl, nd; + + + msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */ + s = *dp; v = f = 0; + bl = 16; /* Max code length */ + do { + if (!msk) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int16_t)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (f) { /* In flag sequence? */ + f = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int16_t)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = s = 0xFF; /* The flag is a data 0xFF */ + } else { + s = *dp; /* Get next data byte */ + if (s == 0xFF) { /* Is start of flag sequence? */ + f = 1; continue; /* Enter flag sequence, get trailing byte */ + } + } + msk = 0x80; /* Read from MSB */ + } + v <<= 1; /* Get a bit */ + if (s & msk) v++; + msk >>= 1; + + for (nd = *hbits++; nd; nd--) { /* Search the code word in this bit length */ + if (v == *hcode++) { /* Matched? */ + jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp; + return *hdata; /* Return the decoded data */ + } + hdata++; + } + bl--; + } while (bl); + + return 0 - (int16_t)JDR_FMT1; /* Err: code not found (may be collapted data) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */ +/*-----------------------------------------------------------------------*/ + +static void block_idct ( + int32_t* src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */ + uint8_t* dst /* Pointer to the destination to store the block as byte array */ +) +{ + const int32_t M13 = (int32_t)(1.41421*4096), M2 = (int32_t)(1.08239*4096), M4 = (int32_t)(2.61313*4096), M5 = (int32_t)(1.84776*4096); + int32_t v0, v1, v2, v3, v4, v5, v6, v7; + int32_t t10, t11, t12, t13; + uint16_t i; + + /* Process columns */ + for (i = 0; i < 8; i++) { + v0 = src[8 * 0]; /* Get even elements */ + v1 = src[8 * 2]; + v2 = src[8 * 4]; + v3 = src[8 * 6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[8 * 7]; /* Get odd elements */ + v5 = src[8 * 1]; + v6 = src[8 * 5]; + v7 = src[8 * 3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + src[8 * 0] = v0 + v7; /* Write-back transformed values */ + src[8 * 7] = v0 - v7; + src[8 * 1] = v1 + v6; + src[8 * 6] = v1 - v6; + src[8 * 2] = v2 + v5; + src[8 * 5] = v2 - v5; + src[8 * 3] = v3 + v4; + src[8 * 4] = v3 - v4; + + src++; /* Next column */ + } + + /* Process rows */ + src -= 8; + for (i = 0; i < 8; i++) { + v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */ + v1 = src[2]; + v2 = src[4]; + v3 = src[6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[7]; /* Get odd elements */ + v5 = src[1]; + v6 = src[5]; + v7 = src[3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + dst[0] = BYTECLIP((v0 + v7) >> 8); /* Descale the transformed values 8 bits and output */ + dst[7] = BYTECLIP((v0 - v7) >> 8); + dst[1] = BYTECLIP((v1 + v6) >> 8); + dst[6] = BYTECLIP((v1 - v6) >> 8); + dst[2] = BYTECLIP((v2 + v5) >> 8); + dst[5] = BYTECLIP((v2 - v5) >> 8); + dst[3] = BYTECLIP((v3 + v4) >> 8); + dst[4] = BYTECLIP((v3 - v4) >> 8); + dst += 8; + + src += 8; /* Next row */ + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load all blocks in the MCU into working buffer */ +/*-----------------------------------------------------------------------*/ + +static JRESULT mcu_load ( + JDEC* jd /* Pointer to the decompressor object */ +) +{ + int32_t *tmp = (int32_t*)jd->workbuf; /* Block working buffer for de-quantize and IDCT */ + int b, d, e; + uint16_t blk, nby, nbc, i, z, id, cmp; + uint8_t *bp; + const uint8_t *hb, *hd; + const uint16_t *hc; + const int32_t *dqf; + + + nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */ + nbc = 2; /* Number of C blocks (2) */ + bp = jd->mcubuf; /* Pointer to the first block */ + + for (blk = 0; blk < nby + nbc; blk++) { + cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */ + id = cmp ? 1 : 0; /* Huffman table ID of the component */ + + /* Extract a DC element from input stream */ + hb = jd->huffbits[id][0]; /* Huffman table for the DC element */ + hc = jd->huffcode[id][0]; + hd = jd->huffdata[id][0]; + b = huffext(jd, hb, hc, hd); /* Extract a huffman coded data (bit length) */ + if (b < 0) return 0 - b; /* Err: invalid code or input */ + d = jd->dcv[cmp]; /* DC value of previous block */ + if (b) { /* If there is any difference from previous block */ + e = bitext(jd, b); /* Extract data bits */ + if (e < 0) return 0 - e; /* Err: input */ + b = 1 << (b - 1); /* MSB position */ + if (!(e & b)) e -= (b << 1) - 1; /* Restore sign if needed */ + d += e; /* Get current value */ + jd->dcv[cmp] = (int16_t)d; /* Save current DC value for next block */ + } + dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */ + tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + + /* Extract following 63 AC elements from input stream */ + for (i = 1; i < 64; tmp[i++] = 0) ; /* Clear rest of elements */ + hb = jd->huffbits[id][1]; /* Huffman table for the AC elements */ + hc = jd->huffcode[id][1]; + hd = jd->huffdata[id][1]; + i = 1; /* Top of the AC elements */ + do { + b = huffext(jd, hb, hc, hd); /* Extract a huffman coded value (zero runs and bit length) */ + if (b == 0) break; /* EOB? */ + if (b < 0) return 0 - b; /* Err: invalid code or input error */ + z = (uint16_t)b >> 4; /* Number of leading zero elements */ + if (z) { + i += z; /* Skip zero elements */ + if (i >= 64) return JDR_FMT1; /* Too long zero run */ + } + if (b &= 0x0F) { /* Bit length */ + d = bitext(jd, b); /* Extract data bits */ + if (d < 0) return 0 - d; /* Err: input device */ + b = 1 << (b - 1); /* MSB position */ + if (!(d & b)) d -= (b << 1) - 1;/* Restore negative value if needed */ + z = ZIG(i); /* Zigzag-order to raster-order converted index */ + tmp[z] = d * dqf[z] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + } + } while (++i < 64); /* Next AC element */ + + if (JD_USE_SCALE && jd->scale == 3) { + *bp = (uint8_t)((*tmp / 256) + 128); /* If scale ratio is 1/8, IDCT can be ommited and only DC element is used */ + } else { + block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */ + } + + bp += 64; /* Next block */ + } + + return JDR_OK; /* All blocks have been loaded successfully */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Output an MCU: Convert YCrCb to RGB and output it in RGB form */ +/*-----------------------------------------------------------------------*/ + +static JRESULT mcu_output ( + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + uint16_t x, /* MCU position in the image (left of the MCU) */ + uint16_t y /* MCU position in the image (top of the MCU) */ +) +{ + const int16_t CVACC = (sizeof (int16_t) > 2) ? 1024 : 128; + uint16_t ix, iy, mx, my, rx, ry; + int16_t yy, cb, cr; + uint8_t *py, *pc, *rgb24; + JRECT rect; + + + mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */ + rx = (x + mx <= jd->width) ? mx : jd->width - x; /* Output rectangular size (it may be clipped at right/bottom end) */ + ry = (y + my <= jd->height) ? my : jd->height - y; + if (JD_USE_SCALE) { + rx >>= jd->scale; ry >>= jd->scale; + if (!rx || !ry) return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */ + x >>= jd->scale; y >>= jd->scale; + } + rect.left = x; rect.right = x + rx - 1; /* Rectangular area in the frame buffer */ + rect.top = y; rect.bottom = y + ry - 1; + + + if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */ + + /* Build an RGB MCU from discrete comopnents */ + rgb24 = (uint8_t*)jd->workbuf; + for (iy = 0; iy < my; iy++) { + pc = jd->mcubuf; + py = pc + iy * 8; + if (my == 16) { /* Double block height? */ + pc += 64 * 4 + (iy >> 1) * 8; + if (iy >= 8) py += 64; + } else { /* Single block height */ + pc += mx * 8 + iy * 8; + } + for (ix = 0; ix < mx; ix++) { + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + if (mx == 16) { /* Double block width? */ + if (ix == 8) py += 64 - 8; /* Jump to next block if double block heigt */ + pc += ix & 1; /* Increase chroma pointer every two pixels */ + } else { /* Single block width */ + pc++; /* Increase chroma pointer every pixel */ + } + yy = *py++; /* Get Y component */ + + /* Convert YCbCr to RGB */ + *rgb24++ = /* R */ BYTECLIP(yy + ((int16_t)(1.402 * CVACC) * cr) / CVACC); + *rgb24++ = /* G */ BYTECLIP(yy - ((int16_t)(0.344 * CVACC) * cb + (int16_t)(0.714 * CVACC) * cr) / CVACC); + *rgb24++ = /* B */ BYTECLIP(yy + ((int16_t)(1.772 * CVACC) * cb) / CVACC); + } + } + + /* Descale the MCU rectangular if needed */ + if (JD_USE_SCALE && jd->scale) { + uint16_t x, y, r, g, b, s, w, a; + uint8_t *op; + + /* Get averaged RGB value of each square correcponds to a pixel */ + s = jd->scale * 2; /* Bumber of shifts for averaging */ + w = 1 << jd->scale; /* Width of square */ + a = (mx - w) * 3; /* Bytes to skip for next line in the square */ + op = (uint8_t*)jd->workbuf; + for (iy = 0; iy < my; iy += w) { + for (ix = 0; ix < mx; ix += w) { + rgb24 = (uint8_t*)jd->workbuf + (iy * mx + ix) * 3; + r = g = b = 0; + for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */ + for (x = 0; x < w; x++) { + r += *rgb24++; + g += *rgb24++; + b += *rgb24++; + } + rgb24 += a; + } /* Put the averaged RGB value as a pixel */ + *op++ = (uint8_t)(r >> s); + *op++ = (uint8_t)(g >> s); + *op++ = (uint8_t)(b >> s); + } + } + } + + } else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */ + + /* Build a 1/8 descaled RGB MCU from discrete comopnents */ + rgb24 = (uint8_t*)jd->workbuf; + pc = jd->mcubuf + mx * my; + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + for (iy = 0; iy < my; iy += 8) { + py = jd->mcubuf; + if (iy == 8) py += 64 * 2; + for (ix = 0; ix < mx; ix += 8) { + yy = *py; /* Get Y component */ + py += 64; + + /* Convert YCbCr to RGB */ + *rgb24++ = /* R */ BYTECLIP(yy + ((int16_t)(1.402 * CVACC) * cr / CVACC)); + *rgb24++ = /* G */ BYTECLIP(yy - ((int16_t)(0.344 * CVACC) * cb + (int16_t)(0.714 * CVACC) * cr) / CVACC); + *rgb24++ = /* B */ BYTECLIP(yy + ((int16_t)(1.772 * CVACC) * cb / CVACC)); + } + } + } + + /* Squeeze up pixel table if a part of MCU is to be truncated */ + mx >>= jd->scale; + if (rx < mx) { + uint8_t *s, *d; + uint16_t x, y; + + s = d = (uint8_t*)jd->workbuf; + for (y = 0; y < ry; y++) { + for (x = 0; x < rx; x++) { /* Copy effective pixels */ + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + } + s += (mx - rx) * 3; /* Skip truncated pixels */ + } + } + + /* Convert RGB888 to RGB565 if needed */ + if (JD_FORMAT == 1) { + uint8_t *s = (uint8_t*)jd->workbuf; + uint16_t w, *d = (uint16_t*)s; + uint16_t n = rx * ry; + + do { + w = (*s++ & 0xF8) << 8; /* RRRRR----------- */ + w |= (*s++ & 0xFC) << 3; /* -----GGGGGG----- */ + w |= *s++ >> 3; /* -----------BBBBB */ + *d++ = w; + } while (--n); + } + + /* Output the RGB rectangular */ + return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Process restart interval */ +/*-----------------------------------------------------------------------*/ + +static JRESULT restart ( + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t rstn /* Expected restert sequense number */ +) +{ + uint16_t i, dc; + uint16_t d; + uint8_t *dp; + + + /* Discard padding bits and get two bytes from the input stream */ + dp = jd->dptr; dc = jd->dctr; + d = 0; + for (i = 0; i < 2; i++) { + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return JDR_INP; + } else { + dp++; + } + dc--; + d = (d << 8) | *dp; /* Get a byte */ + } + jd->dptr = dp; jd->dctr = dc; jd->dmsk = 0; + + /* Check the marker */ + if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7)) { + return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be collapted data) */ + } + + /* Reset DC offset */ + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Analyze the JPEG image and Initialize decompressor object */ +/*-----------------------------------------------------------------------*/ + +#define LDB_WORD(ptr) (uint16_t)(((uint16_t)*((uint8_t*)(ptr))<<8)|(uint16_t)*(uint8_t*)((ptr)+1)) + + +JRESULT jd_prepare ( + JDEC* jd, /* Blank decompressor object */ + uint16_t (*infunc)(JDEC*, uint8_t*, uint16_t), /* JPEG strem input function */ + void* pool, /* Working buffer for the decompression session */ + uint16_t sz_pool, /* Size of working buffer */ + void* dev /* I/O device identifier for the session */ +) +{ + uint8_t *seg, b; + uint16_t marker; + uint32_t ofs; + uint16_t n, i, j, len; + JRESULT rc; + + + if (!pool) return JDR_PAR; + + jd->pool = pool; /* Work memroy */ + jd->sz_pool = sz_pool; /* Size of given work memory */ + jd->infunc = infunc; /* Stream input function */ + jd->device = dev; /* I/O device identifier */ + jd->nrst = 0; /* No restart interval (default) */ + + for (i = 0; i < 2; i++) { /* Nulls pointers */ + for (j = 0; j < 2; j++) { + jd->huffbits[i][j] = 0; + jd->huffcode[i][j] = 0; + jd->huffdata[i][j] = 0; + } + } + for (i = 0; i < 4; jd->qttbl[i++] = 0) ; + + jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */ + if (!seg) return JDR_MEM1; + + if (jd->infunc(jd, seg, 2) != 2) return JDR_INP;/* Check SOI marker */ + if (LDB_WORD(seg) != 0xFFD8) return JDR_FMT1; /* Err: SOI is not detected */ + ofs = 2; + + for (;;) { + /* Get a JPEG marker */ + if (jd->infunc(jd, seg, 4) != 4) return JDR_INP; + marker = LDB_WORD(seg); /* Marker */ + len = LDB_WORD(seg + 2); /* Length field */ + if (len <= 2 || (marker >> 8) != 0xFF) return JDR_FMT1; + len -= 2; /* Content size excluding length field */ + ofs += 4 + len; /* Number of bytes loaded */ + + switch (marker & 0xFF) { + case 0xC0: /* SOF0 (baseline JPEG) */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + jd->width = LDB_WORD(seg+3); /* Image width in unit of pixel */ + jd->height = LDB_WORD(seg+1); /* Image height in unit of pixel */ + if (seg[5] != 3) return JDR_FMT3; /* Err: Supports only Y/Cb/Cr format */ + + /* Check three image components */ + for (i = 0; i < 3; i++) { + b = seg[7 + 3 * i]; /* Get sampling factor */ + if (!i) { /* Y component */ + if (b != 0x11 && b != 0x22 && b != 0x21) { /* Check sampling factor */ + return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */ + } + jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */ + } else { /* Cb/Cr component */ + if (b != 0x11) return JDR_FMT3; /* Err: Sampling factor of Cr/Cb must be 1 */ + } + b = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */ + if (b > 3) return JDR_FMT3; /* Err: Invalid ID */ + jd->qtid[i] = b; + } + break; + + case 0xDD: /* DRI */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Get restart interval (MCUs) */ + jd->nrst = LDB_WORD(seg); + break; + + case 0xC4: /* DHT */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Create huffman tables */ + rc = create_huffman_tbl(jd, seg, len); + if (rc) return rc; + break; + + case 0xDB: /* DQT */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Create de-quantizer tables */ + rc = create_qt_tbl(jd, seg, len); + if (rc) return rc; + break; + + case 0xDA: /* SOS */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + if (!jd->width || !jd->height) return JDR_FMT1; /* Err: Invalid image size */ + + if (seg[0] != 3) return JDR_FMT3; /* Err: Supports only three color components format */ + + /* Check if all tables corresponding to each components have been loaded */ + for (i = 0; i < 3; i++) { + b = seg[2 + 2 * i]; /* Get huffman table ID */ + if (b != 0x00 && b != 0x11) return JDR_FMT3; /* Err: Different table number for DC/AC element */ + b = i ? 1 : 0; + if (!jd->huffbits[b][0] || !jd->huffbits[b][1]) { /* Check dc/ac huffman table for this component */ + return JDR_FMT1; /* Err: Nnot loaded */ + } + if (!jd->qttbl[jd->qtid[i]]) { /* Check dequantizer table for this component */ + return JDR_FMT1; /* Err: Not loaded */ + } + } + + /* Allocate working buffer for MCU and RGB */ + n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */ + if (!n) return JDR_FMT1; /* Err: SOF0 has not been loaded */ + len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */ + if (len < 256) len = 256; /* but at least 256 byte is required for IDCT */ + jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */ + if (!jd->workbuf) return JDR_MEM1; /* Err: not enough memory */ + jd->mcubuf = (uint8_t*)alloc_pool(jd, (uint16_t)((n + 2) * 64)); /* Allocate MCU working buffer */ + if (!jd->mcubuf) return JDR_MEM1; /* Err: not enough memory */ + + /* Pre-load the JPEG data to extract it from the bit stream */ + jd->dptr = seg; jd->dctr = 0; jd->dmsk = 0; /* Prepare to read bit stream */ + if (ofs %= JD_SZBUF) { /* Align read offset to JD_SZBUF */ + jd->dctr = jd->infunc(jd, seg + ofs, (uint16_t)(JD_SZBUF - ofs)); + jd->dptr = seg + ofs - 1; + } + + return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */ + + case 0xC1: /* SOF1 */ + case 0xC2: /* SOF2 */ + case 0xC3: /* SOF3 */ + case 0xC5: /* SOF5 */ + case 0xC6: /* SOF6 */ + case 0xC7: /* SOF7 */ + case 0xC9: /* SOF9 */ + case 0xCA: /* SOF10 */ + case 0xCB: /* SOF11 */ + case 0xCD: /* SOF13 */ + case 0xCE: /* SOF14 */ + case 0xCF: /* SOF15 */ + case 0xD9: /* EOI */ + return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */ + + default: /* Unknown segment (comment, exif or etc..) */ + /* Skip segment data */ + if (jd->infunc(jd, 0, len) != len) { /* Null pointer specifies to skip bytes of stream */ + return JDR_INP; + } + } + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Start to decompress the JPEG picture */ +/*-----------------------------------------------------------------------*/ + +JRESULT jd_decomp ( + JDEC* jd, /* Initialized decompression object */ + uint16_t (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + uint8_t scale, /* Output de-scaling factor (0 to 3) */ + void* display +) +{ + uint16_t x, y, mx, my; + uint16_t rst, rsc; + JRESULT rc; + + + if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; + jd->scale = scale; + jd->_display = display; + + mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ + + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ + rst = rsc = 0; + + rc = JDR_OK; + for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */ + for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */ + if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */ + rc = restart(jd, rsc++); + if (rc != JDR_OK) return rc; + rst = 1; + } + rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream and apply IDCT) */ + if (rc != JDR_OK) return rc; + rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (color space conversion, scaling and output) */ + if (rc != JDR_OK) return rc; + } + } + + return rc; +} + +#else // Now copy with no PROGMEM enforced... + +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor R0.01c (C)ChaN, 2019 +/-----------------------------------------------------------------------------/ +/ The TJpgDec is a generic JPEG decompressor module for tiny embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2019, ChaN, all right reserved. +/ +/ * The TJpgDec module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/-----------------------------------------------------------------------------/ +/ Oct 04, 2011 R0.01 First release. +/ Feb 19, 2012 R0.01a Fixed decompression fails when scan starts with an escape seq. +/ Sep 03, 2012 R0.01b Added JD_TBLCLIP option. +/ Mar 16, 2019 R0.01c Supprted stdint.h. +/----------------------------------------------------------------------------*/ + +#include "tjpgd.h" + + +/*-----------------------------------------------*/ +/* Zigzag-order to raster-order conversion table */ +/*-----------------------------------------------*/ + +#define ZIG(n) Zig[n] + +static const uint8_t Zig[64] = { /* Zigzag-order to raster-order conversion table */ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + + + +/*-------------------------------------------------*/ +/* Input scale factor of Arai algorithm */ +/* (scaled up 16 bits for fixed point operations) */ +/*-------------------------------------------------*/ + +#define IPSF(n) Ipsf[n] + +static const uint16_t Ipsf[64] = { /* See also aa_idct.png */ + (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), + (uint16_t)(1.38704*8192), (uint16_t)(1.92388*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.08979*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.38268*8192), + (uint16_t)(1.30656*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.70711*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.36048*8192), + (uint16_t)(1.17588*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.38268*8192), (uint16_t)(1.17588*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.32442*8192), + (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), + (uint16_t)(0.78570*8192), (uint16_t)(1.08979*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.61732*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.21677*8192), + (uint16_t)(0.54120*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.29290*8192), (uint16_t)(0.14932*8192), + (uint16_t)(0.27590*8192), (uint16_t)(0.38268*8192), (uint16_t)(0.36048*8192), (uint16_t)(0.32442*8192), (uint16_t)(0.27590*8192), (uint16_t)(0.21678*8192), (uint16_t)(0.14932*8192), (uint16_t)(0.07612*8192) +}; + + + +/*---------------------------------------------*/ +/* Conversion table for fast clipping process */ +/*---------------------------------------------*/ + +#if JD_TBLCLIP + +#define BYTECLIP(v) Clip8[(uint16_t)(v) & 0x3FF] + +static const uint8_t Clip8[1024] = { + /* 0..255 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + /* 256..511 */ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + /* -512..-257 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* -256..-1 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#else /* JD_TBLCLIP */ + +inline uint8_t BYTECLIP ( + int16_t val +) +{ + if (val < 0) val = 0; + else if (val > 255) val = 255; // else added by Bodmer to speed things up + + return (uint8_t)val; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Allocate a memory block from memory pool */ +/*-----------------------------------------------------------------------*/ + +static void* alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */ + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t nd /* Number of bytes to allocate */ +) +{ + char *rp = 0; + + + nd = (nd + 3) & ~3; /* Align block size to the word boundary */ + + if (jd->sz_pool >= nd) { + jd->sz_pool -= nd; + rp = (char*)jd->pool; /* Get start of available memory pool */ + jd->pool = (void*)(rp + nd); /* Allocate requierd bytes */ + } + + return (void*)rp; /* Return allocated memory block (NULL:no memory to allocate) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create de-quantization and prescaling tables with a DQT segment */ +/*-----------------------------------------------------------------------*/ + +static int create_qt_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* data, /* Pointer to the quantizer tables */ + uint16_t ndata /* Size of input data */ +) +{ + uint16_t i; + uint8_t d, z; + int32_t *pb; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 65) return JDR_FMT1; /* Err: table size is unaligned */ + ndata -= 65; + d = *data++; /* Get table property */ + if (d & 0xF0) return JDR_FMT1; /* Err: not 8-bit resolution */ + i = d & 3; /* Get table ID */ + pb = alloc_pool(jd, 64 * sizeof (int32_t));/* Allocate a memory block for the table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->qttbl[i] = pb; /* Register the table */ + for (i = 0; i < 64; i++) { /* Load the table */ + z = ZIG(i); /* Zigzag-order to raster-order conversion */ + pb[z] = (int32_t)((uint32_t)*data++ * IPSF(z)); /* Apply scale factor of Arai algorithm to the de-quantizers */ + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create huffman code tables with a DHT segment */ +/*-----------------------------------------------------------------------*/ + +static int create_huffman_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* data, /* Pointer to the packed huffman tables */ + uint16_t ndata /* Size of input data */ +) +{ + uint16_t i, j, b, np, cls, num; + uint8_t d, *pb, *pd; + uint16_t hc, *ph; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 17) return JDR_FMT1; /* Err: wrong data size */ + ndata -= 17; + d = *data++; /* Get table number and class */ + if (d & 0xEE) return JDR_FMT1; /* Err: invalid class/number */ + cls = d >> 4; num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */ + pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->huffbits[num][cls] = pb; + for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */ + np += (pb[i] = *data++); /* Get sum of code words for each code */ + } + ph = alloc_pool(jd, (uint16_t)(np * sizeof (uint16_t)));/* Allocate a memory block for the code word table */ + if (!ph) return JDR_MEM1; /* Err: not enough memory */ + jd->huffcode[num][cls] = ph; + hc = 0; + for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */ + b = pb[i]; + while (b--) ph[j++] = hc++; + hc <<= 1; + } + + if (ndata < np) return JDR_FMT1; /* Err: wrong data size */ + ndata -= np; + pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */ + if (!pd) return JDR_MEM1; /* Err: not enough memory */ + jd->huffdata[num][cls] = pd; + for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code ward */ + d = *data++; + if (!cls && d > 11) return JDR_FMT1; + *pd++ = d; + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract N bits from input stream */ +/*-----------------------------------------------------------------------*/ + +static int bitext ( /* >=0: extracted data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + int nbit /* Number of bits to extract (1 to 11) */ +) +{ + uint8_t msk, s, *dp; + uint16_t dc, v, f; + + + msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */ + s = *dp; v = f = 0; + do { + if (!msk) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int16_t)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (f) { /* In flag sequence? */ + f = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int16_t)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = s = 0xFF; /* The flag is a data 0xFF */ + } else { + s = *dp; /* Get next data byte */ + if (s == 0xFF) { /* Is start of flag sequence? */ + f = 1; continue; /* Enter flag sequence */ + } + } + msk = 0x80; /* Read from MSB */ + } + v <<= 1; /* Get a bit */ + if (s & msk) v++; + msk >>= 1; + nbit--; + } while (nbit); + jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp; + + return (int)v; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract a huffman decoded data from input stream */ +/*-----------------------------------------------------------------------*/ + +static int16_t huffext ( /* >=0: decoded data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* hbits, /* Pointer to the bit distribution table */ + const uint16_t* hcode, /* Pointer to the code word table */ + const uint8_t* hdata /* Pointer to the data table */ +) +{ + uint8_t msk, s, *dp; + uint16_t dc, v, f, bl, nd; + + + msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */ + s = *dp; v = f = 0; + bl = 16; /* Max code length */ + do { + if (!msk) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int16_t)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (f) { /* In flag sequence? */ + f = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int16_t)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = s = 0xFF; /* The flag is a data 0xFF */ + } else { + s = *dp; /* Get next data byte */ + if (s == 0xFF) { /* Is start of flag sequence? */ + f = 1; continue; /* Enter flag sequence, get trailing byte */ + } + } + msk = 0x80; /* Read from MSB */ + } + v <<= 1; /* Get a bit */ + if (s & msk) v++; + msk >>= 1; + + for (nd = *hbits++; nd; nd--) { /* Search the code word in this bit length */ + if (v == *hcode++) { /* Matched? */ + jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp; + return *hdata; /* Return the decoded data */ + } + hdata++; + } + bl--; + } while (bl); + + return 0 - (int16_t)JDR_FMT1; /* Err: code not found (may be collapted data) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */ +/*-----------------------------------------------------------------------*/ + +static void block_idct ( + int32_t* src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */ + uint8_t* dst /* Pointer to the destination to store the block as byte array */ +) +{ + const int32_t M13 = (int32_t)(1.41421*4096), M2 = (int32_t)(1.08239*4096), M4 = (int32_t)(2.61313*4096), M5 = (int32_t)(1.84776*4096); + int32_t v0, v1, v2, v3, v4, v5, v6, v7; + int32_t t10, t11, t12, t13; + uint16_t i; + + /* Process columns */ + for (i = 0; i < 8; i++) { + v0 = src[8 * 0]; /* Get even elements */ + v1 = src[8 * 2]; + v2 = src[8 * 4]; + v3 = src[8 * 6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[8 * 7]; /* Get odd elements */ + v5 = src[8 * 1]; + v6 = src[8 * 5]; + v7 = src[8 * 3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + src[8 * 0] = v0 + v7; /* Write-back transformed values */ + src[8 * 7] = v0 - v7; + src[8 * 1] = v1 + v6; + src[8 * 6] = v1 - v6; + src[8 * 2] = v2 + v5; + src[8 * 5] = v2 - v5; + src[8 * 3] = v3 + v4; + src[8 * 4] = v3 - v4; + + src++; /* Next column */ + } + + /* Process rows */ + src -= 8; + for (i = 0; i < 8; i++) { + v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */ + v1 = src[2]; + v2 = src[4]; + v3 = src[6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[7]; /* Get odd elements */ + v5 = src[1]; + v6 = src[5]; + v7 = src[3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + dst[0] = BYTECLIP((v0 + v7) >> 8); /* Descale the transformed values 8 bits and output */ + dst[7] = BYTECLIP((v0 - v7) >> 8); + dst[1] = BYTECLIP((v1 + v6) >> 8); + dst[6] = BYTECLIP((v1 - v6) >> 8); + dst[2] = BYTECLIP((v2 + v5) >> 8); + dst[5] = BYTECLIP((v2 - v5) >> 8); + dst[3] = BYTECLIP((v3 + v4) >> 8); + dst[4] = BYTECLIP((v3 - v4) >> 8); + dst += 8; + + src += 8; /* Next row */ + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load all blocks in the MCU into working buffer */ +/*-----------------------------------------------------------------------*/ + +static JRESULT mcu_load ( + JDEC* jd /* Pointer to the decompressor object */ +) +{ + int32_t *tmp = (int32_t*)jd->workbuf; /* Block working buffer for de-quantize and IDCT */ + int b, d, e; + uint16_t blk, nby, nbc, i, z, id, cmp; + uint8_t *bp; + const uint8_t *hb, *hd; + const uint16_t *hc; + const int32_t *dqf; + + + nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */ + nbc = 2; /* Number of C blocks (2) */ + bp = jd->mcubuf; /* Pointer to the first block */ + + for (blk = 0; blk < nby + nbc; blk++) { + cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */ + id = cmp ? 1 : 0; /* Huffman table ID of the component */ + + /* Extract a DC element from input stream */ + hb = jd->huffbits[id][0]; /* Huffman table for the DC element */ + hc = jd->huffcode[id][0]; + hd = jd->huffdata[id][0]; + b = huffext(jd, hb, hc, hd); /* Extract a huffman coded data (bit length) */ + if (b < 0) return 0 - b; /* Err: invalid code or input */ + d = jd->dcv[cmp]; /* DC value of previous block */ + if (b) { /* If there is any difference from previous block */ + e = bitext(jd, b); /* Extract data bits */ + if (e < 0) return 0 - e; /* Err: input */ + b = 1 << (b - 1); /* MSB position */ + if (!(e & b)) e -= (b << 1) - 1; /* Restore sign if needed */ + d += e; /* Get current value */ + jd->dcv[cmp] = (int16_t)d; /* Save current DC value for next block */ + } + dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */ + tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + + /* Extract following 63 AC elements from input stream */ + for (i = 1; i < 64; tmp[i++] = 0) ; /* Clear rest of elements */ + hb = jd->huffbits[id][1]; /* Huffman table for the AC elements */ + hc = jd->huffcode[id][1]; + hd = jd->huffdata[id][1]; + i = 1; /* Top of the AC elements */ + do { + b = huffext(jd, hb, hc, hd); /* Extract a huffman coded value (zero runs and bit length) */ + if (b == 0) break; /* EOB? */ + if (b < 0) return 0 - b; /* Err: invalid code or input error */ + z = (uint16_t)b >> 4; /* Number of leading zero elements */ + if (z) { + i += z; /* Skip zero elements */ + if (i >= 64) return JDR_FMT1; /* Too long zero run */ + } + if (b &= 0x0F) { /* Bit length */ + d = bitext(jd, b); /* Extract data bits */ + if (d < 0) return 0 - d; /* Err: input device */ + b = 1 << (b - 1); /* MSB position */ + if (!(d & b)) d -= (b << 1) - 1;/* Restore negative value if needed */ + z = ZIG(i); /* Zigzag-order to raster-order converted index */ + tmp[z] = d * dqf[z] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + } + } while (++i < 64); /* Next AC element */ + + if (JD_USE_SCALE && jd->scale == 3) { + *bp = (uint8_t)((*tmp / 256) + 128); /* If scale ratio is 1/8, IDCT can be ommited and only DC element is used */ + } else { + block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */ + } + + bp += 64; /* Next block */ + } + + return JDR_OK; /* All blocks have been loaded successfully */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Output an MCU: Convert YCrCb to RGB and output it in RGB form */ +/*-----------------------------------------------------------------------*/ + +static JRESULT mcu_output ( + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + uint16_t x, /* MCU position in the image (left of the MCU) */ + uint16_t y /* MCU position in the image (top of the MCU) */ +) +{ + const int16_t CVACC = (sizeof (int16_t) > 2) ? 1024 : 128; + uint16_t ix, iy, mx, my, rx, ry; + int16_t yy, cb, cr; + uint8_t *py, *pc, *rgb24; + JRECT rect; + + + mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */ + rx = (x + mx <= jd->width) ? mx : jd->width - x; /* Output rectangular size (it may be clipped at right/bottom end) */ + ry = (y + my <= jd->height) ? my : jd->height - y; + if (JD_USE_SCALE) { + rx >>= jd->scale; ry >>= jd->scale; + if (!rx || !ry) return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */ + x >>= jd->scale; y >>= jd->scale; + } + rect.left = x; rect.right = x + rx - 1; /* Rectangular area in the frame buffer */ + rect.top = y; rect.bottom = y + ry - 1; + + + if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */ + + /* Build an RGB MCU from discrete comopnents */ + rgb24 = (uint8_t*)jd->workbuf; + for (iy = 0; iy < my; iy++) { + pc = jd->mcubuf; + py = pc + iy * 8; + if (my == 16) { /* Double block height? */ + pc += 64 * 4 + (iy >> 1) * 8; + if (iy >= 8) py += 64; + } else { /* Single block height */ + pc += mx * 8 + iy * 8; + } + for (ix = 0; ix < mx; ix++) { + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + if (mx == 16) { /* Double block width? */ + if (ix == 8) py += 64 - 8; /* Jump to next block if double block heigt */ + pc += ix & 1; /* Increase chroma pointer every two pixels */ + } else { /* Single block width */ + pc++; /* Increase chroma pointer every pixel */ + } + yy = *py++; /* Get Y component */ + + /* Convert YCbCr to RGB */ + *rgb24++ = /* R */ BYTECLIP(yy + ((int16_t)(1.402 * CVACC) * cr) / CVACC); + *rgb24++ = /* G */ BYTECLIP(yy - ((int16_t)(0.344 * CVACC) * cb + (int16_t)(0.714 * CVACC) * cr) / CVACC); + *rgb24++ = /* B */ BYTECLIP(yy + ((int16_t)(1.772 * CVACC) * cb) / CVACC); + } + } + + /* Descale the MCU rectangular if needed */ + if (JD_USE_SCALE && jd->scale) { + uint16_t x, y, r, g, b, s, w, a; + uint8_t *op; + + /* Get averaged RGB value of each square correcponds to a pixel */ + s = jd->scale * 2; /* Bumber of shifts for averaging */ + w = 1 << jd->scale; /* Width of square */ + a = (mx - w) * 3; /* Bytes to skip for next line in the square */ + op = (uint8_t*)jd->workbuf; + for (iy = 0; iy < my; iy += w) { + for (ix = 0; ix < mx; ix += w) { + rgb24 = (uint8_t*)jd->workbuf + (iy * mx + ix) * 3; + r = g = b = 0; + for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */ + for (x = 0; x < w; x++) { + r += *rgb24++; + g += *rgb24++; + b += *rgb24++; + } + rgb24 += a; + } /* Put the averaged RGB value as a pixel */ + *op++ = (uint8_t)(r >> s); + *op++ = (uint8_t)(g >> s); + *op++ = (uint8_t)(b >> s); + } + } + } + + } else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */ + + /* Build a 1/8 descaled RGB MCU from discrete comopnents */ + rgb24 = (uint8_t*)jd->workbuf; + pc = jd->mcubuf + mx * my; + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + for (iy = 0; iy < my; iy += 8) { + py = jd->mcubuf; + if (iy == 8) py += 64 * 2; + for (ix = 0; ix < mx; ix += 8) { + yy = *py; /* Get Y component */ + py += 64; + + /* Convert YCbCr to RGB */ + *rgb24++ = /* R */ BYTECLIP(yy + ((int16_t)(1.402 * CVACC) * cr / CVACC)); + *rgb24++ = /* G */ BYTECLIP(yy - ((int16_t)(0.344 * CVACC) * cb + (int16_t)(0.714 * CVACC) * cr) / CVACC); + *rgb24++ = /* B */ BYTECLIP(yy + ((int16_t)(1.772 * CVACC) * cb / CVACC)); + } + } + } + + /* Squeeze up pixel table if a part of MCU is to be truncated */ + mx >>= jd->scale; + if (rx < mx) { + uint8_t *s, *d; + uint16_t x, y; + + s = d = (uint8_t*)jd->workbuf; + for (y = 0; y < ry; y++) { + for (x = 0; x < rx; x++) { /* Copy effective pixels */ + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + } + s += (mx - rx) * 3; /* Skip truncated pixels */ + } + } + + /* Convert RGB888 to RGB565 if needed */ + if (JD_FORMAT == 1) { + uint8_t *s = (uint8_t*)jd->workbuf; + uint16_t w, *d = (uint16_t*)s; + uint16_t n = rx * ry; + + if (jd->swap) + { + do { + w = (*s++ & 0xF8) << 8; // RRRRR----------- + w |= (*s++ & 0xFC) << 3; // -----GGGGGG----- + w |= *s++ >> 3; // -----------BBBBB + *d++ = (w << 8) | (w >> 8); // Swap bytes + } while (--n); + } + else + { + do { + w = (*s++ & 0xF8) << 8; // RRRRR----------- + w |= (*s++ & 0xFC) << 3; // -----GGGGGG----- + w |= *s++ >> 3; // -----------BBBBB + *d++ = w; + } while (--n); + } + } + + /* Output the RGB rectangular */ + return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Process restart interval */ +/*-----------------------------------------------------------------------*/ + +static JRESULT restart ( + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t rstn /* Expected restert sequense number */ +) +{ + uint16_t i, dc; + uint16_t d; + uint8_t *dp; + + + /* Discard padding bits and get two bytes from the input stream */ + dp = jd->dptr; dc = jd->dctr; + d = 0; + for (i = 0; i < 2; i++) { + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return JDR_INP; + } else { + dp++; + } + dc--; + d = (d << 8) | *dp; /* Get a byte */ + } + jd->dptr = dp; jd->dctr = dc; jd->dmsk = 0; + + /* Check the marker */ + if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7)) { + return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be collapted data) */ + } + + /* Reset DC offset */ + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Analyze the JPEG image and Initialize decompressor object */ +/*-----------------------------------------------------------------------*/ + +#define LDB_WORD(ptr) (uint16_t)(((uint16_t)*((uint8_t*)(ptr))<<8)|(uint16_t)*(uint8_t*)((ptr)+1)) + + +JRESULT jd_prepare ( + JDEC* jd, /* Blank decompressor object */ + uint16_t (*infunc)(JDEC*, uint8_t*, uint16_t), /* JPEG strem input function */ + void* pool, /* Working buffer for the decompression session */ + uint16_t sz_pool, /* Size of working buffer */ + void* dev /* I/O device identifier for the session */ +) +{ + uint8_t *seg, b; + uint16_t marker; + uint32_t ofs; + uint16_t n, i, j, len; + JRESULT rc; + + + if (!pool) return JDR_PAR; + + jd->pool = pool; /* Work memroy */ + jd->sz_pool = sz_pool; /* Size of given work memory */ + jd->infunc = infunc; /* Stream input function */ + jd->device = dev; /* I/O device identifier */ + jd->nrst = 0; /* No restart interval (default) */ + + for (i = 0; i < 2; i++) { /* Nulls pointers */ + for (j = 0; j < 2; j++) { + jd->huffbits[i][j] = 0; + jd->huffcode[i][j] = 0; + jd->huffdata[i][j] = 0; + } + } + for (i = 0; i < 4; jd->qttbl[i++] = 0) ; + + jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */ + if (!seg) return JDR_MEM1; + + if (jd->infunc(jd, seg, 2) != 2) return JDR_INP;/* Check SOI marker */ + if (LDB_WORD(seg) != 0xFFD8) return JDR_FMT1; /* Err: SOI is not detected */ + ofs = 2; + + for (;;) { + /* Get a JPEG marker */ + if (jd->infunc(jd, seg, 4) != 4) return JDR_INP; + marker = LDB_WORD(seg); /* Marker */ + len = LDB_WORD(seg + 2); /* Length field */ + if (len <= 2 || (marker >> 8) != 0xFF) return JDR_FMT1; + len -= 2; /* Content size excluding length field */ + ofs += 4 + len; /* Number of bytes loaded */ + + switch (marker & 0xFF) { + case 0xC0: /* SOF0 (baseline JPEG) */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + jd->width = LDB_WORD(seg+3); /* Image width in unit of pixel */ + jd->height = LDB_WORD(seg+1); /* Image height in unit of pixel */ + if (seg[5] != 3) return JDR_FMT3; /* Err: Supports only Y/Cb/Cr format */ + + /* Check three image components */ + for (i = 0; i < 3; i++) { + b = seg[7 + 3 * i]; /* Get sampling factor */ + if (!i) { /* Y component */ + if (b != 0x11 && b != 0x22 && b != 0x21) { /* Check sampling factor */ + return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */ + } + jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */ + } else { /* Cb/Cr component */ + if (b != 0x11) return JDR_FMT3; /* Err: Sampling factor of Cr/Cb must be 1 */ + } + b = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */ + if (b > 3) return JDR_FMT3; /* Err: Invalid ID */ + jd->qtid[i] = b; + } + break; + + case 0xDD: /* DRI */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Get restart interval (MCUs) */ + jd->nrst = LDB_WORD(seg); + break; + + case 0xC4: /* DHT */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Create huffman tables */ + rc = create_huffman_tbl(jd, seg, len); + if (rc) return rc; + break; + + case 0xDB: /* DQT */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Create de-quantizer tables */ + rc = create_qt_tbl(jd, seg, len); + if (rc) return rc; + break; + + case 0xDA: /* SOS */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + if (!jd->width || !jd->height) return JDR_FMT1; /* Err: Invalid image size */ + + if (seg[0] != 3) return JDR_FMT3; /* Err: Supports only three color components format */ + + /* Check if all tables corresponding to each components have been loaded */ + for (i = 0; i < 3; i++) { + b = seg[2 + 2 * i]; /* Get huffman table ID */ + if (b != 0x00 && b != 0x11) return JDR_FMT3; /* Err: Different table number for DC/AC element */ + b = i ? 1 : 0; + if (!jd->huffbits[b][0] || !jd->huffbits[b][1]) { /* Check dc/ac huffman table for this component */ + return JDR_FMT1; /* Err: Nnot loaded */ + } + if (!jd->qttbl[jd->qtid[i]]) { /* Check dequantizer table for this component */ + return JDR_FMT1; /* Err: Not loaded */ + } + } + + /* Allocate working buffer for MCU and RGB */ + n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */ + if (!n) return JDR_FMT1; /* Err: SOF0 has not been loaded */ + len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */ + if (len < 256) len = 256; /* but at least 256 byte is required for IDCT */ + jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */ + if (!jd->workbuf) return JDR_MEM1; /* Err: not enough memory */ + jd->mcubuf = (uint8_t*)alloc_pool(jd, (uint16_t)((n + 2) * 64)); /* Allocate MCU working buffer */ + if (!jd->mcubuf) return JDR_MEM1; /* Err: not enough memory */ + + /* Pre-load the JPEG data to extract it from the bit stream */ + jd->dptr = seg; jd->dctr = 0; jd->dmsk = 0; /* Prepare to read bit stream */ + if (ofs %= JD_SZBUF) { /* Align read offset to JD_SZBUF */ + jd->dctr = jd->infunc(jd, seg + ofs, (uint16_t)(JD_SZBUF - ofs)); + jd->dptr = seg + ofs - 1; + } + + return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */ + + case 0xC1: /* SOF1 */ + case 0xC2: /* SOF2 */ + case 0xC3: /* SOF3 */ + case 0xC5: /* SOF5 */ + case 0xC6: /* SOF6 */ + case 0xC7: /* SOF7 */ + case 0xC9: /* SOF9 */ + case 0xCA: /* SOF10 */ + case 0xCB: /* SOF11 */ + case 0xCD: /* SOF13 */ + case 0xCE: /* SOF14 */ + case 0xCF: /* SOF15 */ + case 0xD9: /* EOI */ + return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */ + + default: /* Unknown segment (comment, exif or etc..) */ + /* Skip segment data */ + if (jd->infunc(jd, 0, len) != len) { /* Null pointer specifies to skip bytes of stream */ + return JDR_INP; + } + } + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Start to decompress the JPEG picture */ +/*-----------------------------------------------------------------------*/ + +JRESULT jd_decomp ( + JDEC* jd, /* Initialized decompression object */ + uint16_t (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + uint8_t scale, /* Output de-scaling factor (0 to 3) */ + void* display +) +{ + uint16_t x, y, mx, my; + uint16_t rst, rsc; + JRESULT rc; + + + if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; + jd->scale = scale; + jd->_display = display; + + mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ + + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ + rst = rsc = 0; + + rc = JDR_OK; + for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */ + for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */ + if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */ + rc = restart(jd, rsc++); + if (rc != JDR_OK) return rc; + rst = 1; + } + rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream and apply IDCT) */ + if (rc != JDR_OK) return rc; + rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (color space conversion, scaling and output) */ + if (rc != JDR_OK) return rc; + } + } + + return rc; +} + +#endif diff --git a/tjpgd.h b/tjpgd.h new file mode 100644 index 0000000..ed26f5b --- /dev/null +++ b/tjpgd.h @@ -0,0 +1,91 @@ +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor include file (C)ChaN, 2019 +/----------------------------------------------------------------------------*/ +#ifndef DEF_TJPGDEC +#define DEF_TJPGDEC +/*---------------------------------------------------------------------------*/ +/* System Configurations */ + +#define JD_SZBUF 512 /* Size of stream input buffer */ +#define JD_FORMAT 1 /* Output pixel format 0:RGB888 (3 BYTE/pix), 1:RGB565 (1 WORD/pix) */ +#define JD_USE_SCALE 1 /* Use descaling feature for output */ +#ifdef ESP32 // Table gives no speed mprovement for ESP32 + #define JD_TBLCLIP 0 /* Use table for saturation (might be a bit faster but increases 1K bytes of code size) */ +#else + #define JD_TBLCLIP 1 /* Use table for saturation (might be a bit faster but increases 1K bytes of code size) */ +#endif +/*---------------------------------------------------------------------------*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) /* Main development platform */ +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef short int16_t; +typedef unsigned long uint32_t; +typedef long int32_t; +#else +#include "stdint.h" +#endif + +/* Error code */ +typedef enum { + JDR_OK = 0, /* 0: Succeeded */ + JDR_INTR, /* 1: Interrupted by output function */ + JDR_INP, /* 2: Device error or wrong termination of input stream */ + JDR_MEM1, /* 3: Insufficient memory pool for the image */ + JDR_MEM2, /* 4: Insufficient stream input buffer */ + JDR_PAR, /* 5: Parameter error */ + JDR_FMT1, /* 6: Data format error (may be damaged data) */ + JDR_FMT2, /* 7: Right format but not supported */ + JDR_FMT3 /* 8: Not supported JPEG standard */ +} JRESULT; + + + +/* Rectangular structure */ +typedef struct { + uint16_t left, right, top, bottom; +} JRECT; + + + +/* Decompressor object structure */ +typedef struct JDEC_s JDEC; +struct JDEC_s { + uint16_t dctr; /* Number of bytes available in the input buffer */ + uint8_t* dptr; /* Current data read ptr */ + uint8_t* inbuf; /* Bit stream input buffer */ + uint8_t dmsk; /* Current bit in the current read byte */ + uint8_t scale; /* Output scaling ratio */ + uint8_t msx, msy; /* MCU size in unit of block (width, height) */ + uint8_t qtid[3]; /* Quantization table ID of each component */ + int16_t dcv[3]; /* Previous DC element of each component */ + uint16_t nrst; /* Restart inverval */ + uint16_t width, height; /* Size of the input image (pixel) */ + uint8_t* huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */ + uint16_t* huffcode[2][2]; /* Huffman code word tables [id][dcac] */ + uint8_t* huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */ + int32_t* qttbl[4]; /* Dequantizer tables [id] */ + void* workbuf; /* Working buffer for IDCT and RGB output */ + uint8_t* mcubuf; /* Working buffer for the MCU */ + void* pool; /* Pointer to available memory pool */ + uint16_t sz_pool; /* Size of momory pool (bytes available) */ + uint16_t (*infunc)(JDEC*, uint8_t*, uint16_t);/* Pointer to jpeg stream input function */ + void* device; /* Pointer to I/O device identifiler for the session */ + uint8_t swap; /* Added by Bodmer to control byte swapping */ + void* _display; +}; + +/* TJpgDec API functions */ +JRESULT jd_prepare (JDEC*, uint16_t(*)(JDEC*,uint8_t*,uint16_t), void*, uint16_t, void*); +JRESULT jd_decomp (JDEC*, uint16_t(*)(JDEC*,void*,JRECT*), uint8_t, void*); + + +#ifdef __cplusplus +} +#endif + +#endif /* _TJPGDEC */ From 1cdda3907294ed41c9d717c32e0d1b705397034a Mon Sep 17 00:00:00 2001 From: Thorinair Date: Mon, 31 Aug 2020 12:58:57 +0200 Subject: [PATCH 2/5] Rename Web BPM example --- .../10-Inkplate_Web_BMP_pictures.ino} | 2 +- .../neowise.bmp | Bin .../neowise_mono.bmp | Bin 3 files changed, 1 insertion(+), 1 deletion(-) rename examples/2. Advanced Inkplate Features/{10-Inkplate_Download_And_Show/10-Inkplate_Download_And_Show.ino => 10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino} (98%) rename examples/2. Advanced Inkplate Features/{10-Inkplate_Download_And_Show => 10-Inkplate_Web_BMP_pictures}/neowise.bmp (100%) rename examples/2. Advanced Inkplate Features/{10-Inkplate_Download_And_Show => 10-Inkplate_Web_BMP_pictures}/neowise_mono.bmp (100%) diff --git a/examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/10-Inkplate_Download_And_Show.ino b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino similarity index 98% rename from examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/10-Inkplate_Download_And_Show.ino rename to examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino index 5a156a0..431c311 100644 --- a/examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/10-Inkplate_Download_And_Show.ino +++ b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino @@ -1,5 +1,5 @@ /* - 10_Inkplate_Download_And_Show example for e-radionica Inkplate6 + 10_Web_BMP_pictures example for e-radionica Inkplate6 For this example you will need a micro USB cable, Inkplate6, and an available WiFi connection. Select "Inkplate 6(ESP32)" from Tools -> Board menu. Don't have "Inkplate 6(ESP32)" option? Follow our tutorial and add it: diff --git a/examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/neowise.bmp b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/neowise.bmp similarity index 100% rename from examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/neowise.bmp rename to examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/neowise.bmp diff --git a/examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/neowise_mono.bmp b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/neowise_mono.bmp similarity index 100% rename from examples/2. Advanced Inkplate Features/10-Inkplate_Download_And_Show/neowise_mono.bmp rename to examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/neowise_mono.bmp From e904398dcab125d590f6a932e6cb4ed7390ff02f Mon Sep 17 00:00:00 2001 From: Thorinair Date: Mon, 31 Aug 2020 13:11:08 +0200 Subject: [PATCH 3/5] Clean up example --- Inkplate.cpp | 1 + .../11-Inkplate_SD_JPEG_pictures.ino | 25 ++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Inkplate.cpp b/Inkplate.cpp index 6e78c91..392b5c0 100644 --- a/Inkplate.cpp +++ b/Inkplate.cpp @@ -557,6 +557,7 @@ int Inkplate::drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool di pnt += read; } } + p->close(); //TJpgDec.getJpgSize(&w, &h, buf, total); //Serial.print("Width = "); Serial.print(w); Serial.print(", height = "); Serial.println(h); diff --git a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino index 671ecde..95741ad 100644 --- a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino +++ b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino @@ -1,7 +1,7 @@ /* - 5_Inkplate_SD_BMP example for e-radionica Inkplate6 + 5_Inkplate_SD_JPEG_pictures example for e-radionica Inkplate6 For this example you will need a micro USB cable, Inkplate6 and a SD card loaded with - image1.bmp and image2.bmp file that can be found inside folder of this example. + pyramid.jpg file that can be found inside folder of this example. Select "Inkplate 6(ESP32)" from Tools -> Board menu. Don't have "Inkplate 6(ESP32)" option? Follow our tutorial and add it: https://e-radionica.com/en/blog/add-inkplate-6-to-arduino-ide/ @@ -9,16 +9,15 @@ To work with SD card on Inkplate, you will need to add one extra library. Download and install it from here: https://github.com/e-radionicacom/Inkplate-6-SDFat-Arduino-Library - You can open .bmp files that have color depth of 1 bit (monochrome bitmap), 4 bit, 8 bit and - 24 bit AND have resoluton smaller than 800x600 or otherwise it won't fit on screen. + You can open .jpg files that have resoluton smaller than 800x600 or otherwise it won't fit on screen. Format your SD card in standard FAT fileformat. - This example will show you how you can read .bmp files (pictures) from SD card and + This example will show you how you can read a .jpg file (picture) from SD card and display that image on e-paper display. Want to learn more about Inkplate? Visit www.inkplate.io Looking to get support? Write on our forums: http://forum.e-radionica.com/en/ - 15 July 2020 by e-radionica.com + 31 August 2020 by e-radionica.com */ #include "Inkplate.h" //Include Inkplate library to the sketch @@ -38,14 +37,12 @@ void setup() { display.println("SD Card OK! Reading image..."); display.partialUpdate(); - //If card is properly init, try to load image and display it on e-paper at position X=0, Y=0 - //NOTE: Both drawBitmapFromSD methods allow for an optional fourth "invert" parameter. Setting this parameter to true - //will flip all colors on the image, making black white and white black. This may be necessary when exporting bitmaps from - //certain softwares. - if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, 0)) { - //If is something failed (wrong filename or wrong bitmap format), write error message on the screen. - //REMEMBER! You can only use Windows Bitmap file with color depth of 1, 4, 8 or 24 bits with no compression! - //You can turn of dithering for somewhat faster image load by changing the last 1 to 0, or removing the 1 argument completely + //If card is properly init, try to load image and display it on e-paper at position X=100, Y=0 + //NOTE: Both drawJpegFromSD methods allow for an optional fourth "invert" parameter. Setting this parameter to true + //will flip all colors on the image, making black white and white black. + if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, 0, 1)) { + //If is something failed (wrong filename or wrong format), write error message on the screen. + //You can turn off dithering for somewhat faster image load by changing the last 1 to 0, or removing the 1 argument completely display.println("Image open error"); display.display(); } From cef06b82d6accd7a14b245229424fcf4703650bd Mon Sep 17 00:00:00 2001 From: Thorinair Date: Mon, 31 Aug 2020 13:35:30 +0200 Subject: [PATCH 4/5] Add web JPEG support --- Inkplate.cpp | 70 +++++++++++++++++-- Inkplate.h | 14 ++-- .../10-Inkplate_Web_BMP_pictures.ino | 2 +- .../11-Inkplate_SD_JPEG_pictures.ino | 8 ++- .../12-Inkplate_Web_JPEG_pictures.ino | 62 ++++++++++++++++ 5 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino diff --git a/Inkplate.cpp b/Inkplate.cpp index 392b5c0..8b32300 100644 --- a/Inkplate.cpp +++ b/Inkplate.cpp @@ -538,7 +538,7 @@ int Inkplate::drawBitmapFromWeb(char *url, int x, int y, bool dither, bool inver int Inkplate::drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool dither, bool invert) { - uint16_t w = 0, h = 0; + uint8_t ret = 0; TJpgDec.setJpgScale(1); TJpgDec.setCallback(jpegCallback); @@ -559,13 +559,14 @@ int Inkplate::drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool di } p->close(); - //TJpgDec.getJpgSize(&w, &h, buf, total); - //Serial.print("Width = "); Serial.print(w); Serial.print(", height = "); Serial.println(h); selectDisplayMode(INKPLATE_3BIT); - TJpgDec.drawJpg(x, y, buf, total, display); + if(TJpgDec.drawJpg(x, y, buf, total, display) == 0) + ret = 1; free(buf); + + return ret; } int Inkplate::drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool dither, bool invert) @@ -583,6 +584,67 @@ int Inkplate::drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bo } } +int Inkplate::drawJpegFromWeb(Inkplate *display, WiFiClient *s, int x, int y, int len, bool dither, bool invert) +{ + uint8_t ret = 0; + + TJpgDec.setJpgScale(1); + TJpgDec.setCallback(jpegCallback); + + uint32_t pnt = 0; + uint8_t *buf = (uint8_t *)ps_malloc(len); + if (buf == NULL) + return 0; + + while (pnt < len) { + uint32_t toread = s->available(); + if (toread > 0) { + int read = s->read(buf + pnt, toread); + if (read > 0) + pnt += read; + } + } + + selectDisplayMode(INKPLATE_3BIT); + + if(TJpgDec.drawJpg(x, y, buf, len, display) == 0) + ret = 1; + + free(buf); + + return ret; +} + +int Inkplate::drawJpegFromWeb(Inkplate *display, char *url, int x, int y, bool dither, bool invert) +{ + if (WiFi.status() != WL_CONNECTED) + return 0; + int ret = 0; + + bool sleep = WiFi.getSleep(); + WiFi.setSleep(false); + + HTTPClient http; + http.getStream().setNoDelay(true); + http.getStream().setTimeout(1); + http.begin(url); + + int httpCode = http.GET(); + if (httpCode == 200) + { + int32_t len = http.getSize(); + if (len > 0) + { + WiFiClient *dat = http.getStreamPtr(); + ret = drawJpegFromWeb(display, dat, x, y, len, dither, invert); + } + } + + http.end(); + WiFi.setSleep(sleep); + return ret; +} + void Inkplate::drawElipse(int rx, int ry, int xc, int yc, int c) diff --git a/Inkplate.h b/Inkplate.h index 9d0186c..cd88bbd 100644 --- a/Inkplate.h +++ b/Inkplate.h @@ -233,12 +233,14 @@ public: void einkOn(void); void selectDisplayMode(uint8_t _mode); uint8_t getDisplayMode(); - int drawBitmapFromSD(SdFile *p, int x, int y, bool dither = false, bool invert = false); - int drawBitmapFromSD(char *fileName, int x, int y, bool dither = false, bool invert = false); - int drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool dither = false, bool invert = false); - int drawBitmapFromWeb(char *url, int x, int y, bool dither = false, bool invert = false); - int drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool dither = false, bool invert = false); - int drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool dither = false, bool invert = false); + int drawBitmapFromSD(SdFile *p, int x, int y, bool invert = false, bool dither = false); + int drawBitmapFromSD(char *fileName, int x, int y, bool invert = false, bool dither = false); + int drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool invert = false, bool dither = false); + int drawBitmapFromWeb(char *url, int x, int y, bool invert = false, bool dither = false); + int drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool invert = false, bool dither = false); + int drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool invert = false, bool dither = false); + int drawJpegFromWeb(Inkplate *display, WiFiClient *s, int x, int y, int len, bool invert = false, bool dither = false); + int drawJpegFromWeb(Inkplate *display, char *url, int x, int y, bool invert = false, bool dither = false); void drawElipse(int rx, int ry, int xc, int yc, int c); void fillElipse(int rx, int ry, int xc, int yc, int c); void drawPolygon(int *x, int *y, int n, int color); diff --git a/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino index 431c311..43c8054 100644 --- a/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino +++ b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino @@ -22,7 +22,7 @@ Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome) const char* ssid = ""; //Your WiFi SSID -const char* password = ""; //Your WiFi password +const char* password = ""; //Your WiFi password void setup() { display.begin(); //Init Inkplate library (you should call this function ONLY ONCE) diff --git a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino index 95741ad..1e6cc32 100644 --- a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino +++ b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino @@ -38,11 +38,13 @@ void setup() { display.partialUpdate(); //If card is properly init, try to load image and display it on e-paper at position X=100, Y=0 - //NOTE: Both drawJpegFromSD methods allow for an optional fourth "invert" parameter. Setting this parameter to true + //NOTE: These methods require you to pass a reference to the display object as first parameter. + //NOTE: Both drawJpegFromSD methods allow for an optional fifth "invert" parameter. Setting this parameter to true //will flip all colors on the image, making black white and white black. - if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, 0, 1)) { + //Sixth parameter will dither the image. + if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, false, true)) { //If is something failed (wrong filename or wrong format), write error message on the screen. - //You can turn off dithering for somewhat faster image load by changing the last 1 to 0, or removing the 1 argument completely + //You can turn off dithering for somewhat faster image load by changing the last true to false, or removing the argument completely display.println("Image open error"); display.display(); } diff --git a/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino b/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino new file mode 100644 index 0000000..9ed62ca --- /dev/null +++ b/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino @@ -0,0 +1,62 @@ +/* + 12-Inkplate_Web_JPEG_pictures for e-radionica Inkplate6 + For this example you will need a micro USB cable, Inkplate6, and an available WiFi connection. + Select "Inkplate 6(ESP32)" from Tools -> Board menu. + Don't have "Inkplate 6(ESP32)" option? Follow our tutorial and add it: + https://e-radionica.com/en/blog/add-inkplate-6-to-arduino-ide/ + + You can open .jpg files that have resoluton smaller than 800x600 or otherwise it won't fit on screen. + + This example will show you how you can download a .jpg file (picture) from the web and + display that image on e-paper display. + + Want to learn more about Inkplate? Visit www.inkplate.io + Looking to get support? Write on our forums: http://forum.e-radionica.com/en/ + 31 August 2020 by e-radionica.com +*/ + +#include "Inkplate.h" //Include Inkplate library to the sketch +#include "HTTPClient.h" //Include library for HTTPClient +#include "WiFi.h" //Include library for WiFi +Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome) + +const char* ssid = "Twilight Sparkle"; //Your WiFi SSID +const char* password = "thori4twily"; //Your WiFi password + +void setup() { + display.begin(); //Init Inkplate library (you should call this function ONLY ONCE) + display.clearDisplay(); //Clear frame buffer of display + display.display(); //Put clear image on display + + display.print("Connecting to WiFi..."); + display.partialUpdate(); + + //Connect to the WiFi network. + WiFi.mode(WIFI_MODE_STA); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + display.print("."); + display.partialUpdate(); + } + display.println("\nWiFi OK! Downloading..."); + display.partialUpdate(); + + //Try to load image and display it on e-paper at position X=0, Y=100 + //NOTE: These methods require you to pass a reference to the display object as first parameter. + //NOTE: Both drawJpegFromWeb methods allow for an optional fifth "invert" parameter. Setting this parameter to true + //will flip all colors on the image, making black white and white black. + //Sixth parameter will dither the image. + if (!display.drawJpegFromWeb(&display, "https://varipass.org/destination.jpg", 0, 100, false, true)) { + //If is something failed (wrong filename or format), write error message on the screen. + display.println("Image open error"); + display.display(); + } + display.display(); + + WiFi.mode(WIFI_OFF); +} + +void loop() { + //Nothing... +} From 0fe94b5867227a3a5ce41f64c9087efe259d2d91 Mon Sep 17 00:00:00 2001 From: Thorinair Date: Mon, 31 Aug 2020 14:07:56 +0200 Subject: [PATCH 5/5] Add invert support, add dither starter --- Inkplate.cpp | 13 ++++++++++--- Inkplate.h | 16 ++++++++-------- TJpg_Decoder.cpp | 6 +++--- TJpg_Decoder.h | 4 ++-- .../10-Inkplate_Web_BMP_pictures.ino | 2 +- .../11-Inkplate_SD_JPEG_pictures.ino | 10 ++++------ .../12-Inkplate_Web_JPEG_pictures.ino | 11 ++++++----- tjpgd.c | 12 ++++++++++-- tjpgd.h | 4 +++- 9 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Inkplate.cpp b/Inkplate.cpp index 8b32300..2a6e916 100644 --- a/Inkplate.cpp +++ b/Inkplate.cpp @@ -32,15 +32,22 @@ void ckvClock() usleep1(); } -bool jpegCallback(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* data, void* _display) { +bool jpegCallback(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* data, void* _display, bool _dither, bool _invert) { Inkplate *display = static_cast(_display); + if (_dither) { + //TODO: Implement dithering! + Serial.println(_dither); + } + int i, j; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { uint16_t rgb = data[j*w + i]; + if (_invert) + rgb = ~rgb; uint8_t px = (RED(rgb) * 2126 / 10000) + (GREEN(rgb) * 7152 / 10000) + (BLUE(rgb) * 722 / 10000); display->drawPixel(i + x, j + y, px >> 5); } @@ -561,7 +568,7 @@ int Inkplate::drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool di selectDisplayMode(INKPLATE_3BIT); - if(TJpgDec.drawJpg(x, y, buf, total, display) == 0) + if(TJpgDec.drawJpg(x, y, buf, total, display, dither, invert) == 0) ret = 1; free(buf); @@ -607,7 +614,7 @@ int Inkplate::drawJpegFromWeb(Inkplate *display, WiFiClient *s, int x, int y, in selectDisplayMode(INKPLATE_3BIT); - if(TJpgDec.drawJpg(x, y, buf, len, display) == 0) + if(TJpgDec.drawJpg(x, y, buf, len, display, dither, invert) == 0) ret = 1; free(buf); diff --git a/Inkplate.h b/Inkplate.h index cd88bbd..3237317 100644 --- a/Inkplate.h +++ b/Inkplate.h @@ -233,14 +233,14 @@ public: void einkOn(void); void selectDisplayMode(uint8_t _mode); uint8_t getDisplayMode(); - int drawBitmapFromSD(SdFile *p, int x, int y, bool invert = false, bool dither = false); - int drawBitmapFromSD(char *fileName, int x, int y, bool invert = false, bool dither = false); - int drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool invert = false, bool dither = false); - int drawBitmapFromWeb(char *url, int x, int y, bool invert = false, bool dither = false); - int drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool invert = false, bool dither = false); - int drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool invert = false, bool dither = false); - int drawJpegFromWeb(Inkplate *display, WiFiClient *s, int x, int y, int len, bool invert = false, bool dither = false); - int drawJpegFromWeb(Inkplate *display, char *url, int x, int y, bool invert = false, bool dither = false); + int drawBitmapFromSD(SdFile *p, int x, int y, bool dither = false, bool invert = false); + int drawBitmapFromSD(char *fileName, int x, int y, bool dither = false, bool invert = false); + int drawBitmapFromWeb(WiFiClient *s, int x, int y, int len, bool dither = false, bool invert = false); + int drawBitmapFromWeb(char *url, int x, int y, bool dither = false, bool invert = false); + int drawJpegFromSD(Inkplate *display, SdFile *p, int x, int y, bool dither = false, bool invert = false); + int drawJpegFromSD(Inkplate *display, char *fileName, int x, int y, bool dither = false, bool invert = false); + int drawJpegFromWeb(Inkplate *display, WiFiClient *s, int x, int y, int len, bool dither = false, bool invert = false); + int drawJpegFromWeb(Inkplate *display, char *url, int x, int y, bool dither = false, bool invert = false); void drawElipse(int rx, int ry, int xc, int yc, int c); void fillElipse(int rx, int ry, int xc, int yc, int c); void drawPolygon(int *x, int *y, int n, int color); diff --git a/TJpg_Decoder.cpp b/TJpg_Decoder.cpp index fe76eb5..942b4be 100644 --- a/TJpg_Decoder.cpp +++ b/TJpg_Decoder.cpp @@ -116,14 +116,14 @@ uint16_t TJpg_Decoder::jd_output(JDEC* jdec, void* bitmap, JRECT* jrect) uint16_t h = jrect->bottom + 1 - jrect->top; // Pass the image block and rendering parameters in a callback to the sketch - return thisPtr->tft_output(x, y, w, h, (uint16_t*)bitmap, (void*)jdec->_display); + return thisPtr->tft_output(x, y, w, h, (uint16_t*)bitmap, (void*)jdec->_display, jdec->_dither, jdec->_invert); } /*************************************************************************************** ** Function name: drawJpg ** Description: Draw a jpg saved in a FLASH memory array ***************************************************************************************/ -JRESULT TJpg_Decoder::drawJpg(int32_t x, int32_t y, const uint8_t jpeg_data[], uint32_t data_size, void* display) { +JRESULT TJpg_Decoder::drawJpg(int32_t x, int32_t y, const uint8_t jpeg_data[], uint32_t data_size, void* display, bool dither, bool invert) { JDEC jdec; JRESULT jresult = JDR_OK; @@ -142,7 +142,7 @@ JRESULT TJpg_Decoder::drawJpg(int32_t x, int32_t y, const uint8_t jpeg_data[], u // Extract image and render if (jresult == JDR_OK) { - jresult = jd_decomp(&jdec, jd_output, jpgScale, display); + jresult = jd_decomp(&jdec, jd_output, jpgScale, display, dither, invert); } return jresult; diff --git a/TJpg_Decoder.h b/TJpg_Decoder.h index 69fbf7d..9bdd1b3 100644 --- a/TJpg_Decoder.h +++ b/TJpg_Decoder.h @@ -30,7 +30,7 @@ enum { //------------------------------------------------------------------------------ -typedef bool (*SketchCallback)(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *data, void* display); +typedef bool (*SketchCallback)(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *data, void* display, bool dither, bool invert); class TJpg_Decoder { @@ -47,7 +47,7 @@ public: void setJpgScale(uint8_t scale); void setCallback(SketchCallback sketchCallback); - JRESULT drawJpg(int32_t x, int32_t y, const uint8_t array[], uint32_t array_size, void* display); + JRESULT drawJpg(int32_t x, int32_t y, const uint8_t array[], uint32_t array_size, void* display, bool dither, bool invert); JRESULT getJpgSize(uint16_t *w, uint16_t *h, const uint8_t array[], uint32_t array_size); void setSwapBytes(bool swap); diff --git a/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino index 43c8054..a2ef00f 100644 --- a/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino +++ b/examples/2. Advanced Inkplate Features/10-Inkplate_Web_BMP_pictures/10-Inkplate_Web_BMP_pictures.ino @@ -52,7 +52,7 @@ void setup() { //Photo taken by: Roberto Fernandez if (!display.drawBitmapFromWeb("https://varipass.org/neowise_mono.bmp", 0, 0, false, true)) { //If is something failed (wrong filename or wrong bitmap format), write error message on the screen. - //REMEMBER! You can only use Windows Bitmap file with color depth of 1, 4, 8 or 24 bits with no compression! + //REMEMBER! You can only use Windows Bitmap file with color depth of 1, 4, 8 or 24 bits with no compression! display.println("Image open error"); display.display(); } diff --git a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino index 1e6cc32..60599fb 100644 --- a/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino +++ b/examples/2. Advanced Inkplate Features/11-Inkplate_SD_JPEG_pictures/11-Inkplate_SD_JPEG_pictures.ino @@ -26,8 +26,6 @@ Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and a SdFile file; //Create SdFile object used for accessing files on SD card void setup() { - Serial.begin(115200); - display.begin(); //Init Inkplate library (you should call this function ONLY ONCE) display.clearDisplay(); //Clear frame buffer of display display.display(); //Put clear image on display @@ -39,12 +37,12 @@ void setup() { //If card is properly init, try to load image and display it on e-paper at position X=100, Y=0 //NOTE: These methods require you to pass a reference to the display object as first parameter. - //NOTE: Both drawJpegFromSD methods allow for an optional fifth "invert" parameter. Setting this parameter to true + //NOTE: Both drawJpegFromSD methods allow for an optional sixth "invert" parameter. Setting this parameter to true //will flip all colors on the image, making black white and white black. - //Sixth parameter will dither the image. - if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, false, true)) { + //fifth parameter will dither the image. + if (!display.drawJpegFromSD(&display, "pyramid.jpg", 100, 0, true, false)) { //If is something failed (wrong filename or wrong format), write error message on the screen. - //You can turn off dithering for somewhat faster image load by changing the last true to false, or removing the argument completely + //You can turn off dithering for somewhat faster image load by changing the fifth parameter to false, or removing the parameter completely display.println("Image open error"); display.display(); } diff --git a/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino b/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino index 9ed62ca..a002961 100644 --- a/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino +++ b/examples/2. Advanced Inkplate Features/12-Inkplate_Web_JPEG_pictures/12-Inkplate_Web_JPEG_pictures.ino @@ -20,10 +20,11 @@ #include "WiFi.h" //Include library for WiFi Inkplate display(INKPLATE_1BIT); //Create an object on Inkplate library and also set library into 1 Bit mode (Monochrome) -const char* ssid = "Twilight Sparkle"; //Your WiFi SSID -const char* password = "thori4twily"; //Your WiFi password +const char* ssid = ""; //Your WiFi SSID +const char* password = ""; //Your WiFi password void setup() { + Serial.begin(115200); display.begin(); //Init Inkplate library (you should call this function ONLY ONCE) display.clearDisplay(); //Clear frame buffer of display display.display(); //Put clear image on display @@ -44,10 +45,10 @@ void setup() { //Try to load image and display it on e-paper at position X=0, Y=100 //NOTE: These methods require you to pass a reference to the display object as first parameter. - //NOTE: Both drawJpegFromWeb methods allow for an optional fifth "invert" parameter. Setting this parameter to true + //NOTE: Both drawJpegFromWeb methods allow for an optional sisxth "invert" parameter. Setting this parameter to true //will flip all colors on the image, making black white and white black. - //Sixth parameter will dither the image. - if (!display.drawJpegFromWeb(&display, "https://varipass.org/destination.jpg", 0, 100, false, true)) { + //fifth parameter will dither the image. + if (!display.drawJpegFromWeb(&display, "https://varipass.org/destination.jpg", 0, 100, true, false)) { //If is something failed (wrong filename or format), write error message on the screen. display.println("Image open error"); display.display(); diff --git a/tjpgd.c b/tjpgd.c index 301c5d0..4b3321e 100644 --- a/tjpgd.c +++ b/tjpgd.c @@ -947,7 +947,9 @@ JRESULT jd_decomp ( JDEC* jd, /* Initialized decompression object */ uint16_t (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ uint8_t scale, /* Output de-scaling factor (0 to 3) */ - void* display + void* display, + uint8_t dither, + uint8_t invert ) { uint16_t x, y, mx, my; @@ -958,6 +960,8 @@ JRESULT jd_decomp ( if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; jd->scale = scale; jd->_display = display; + jd->_dither = dither; + jd->_invert = invert; mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ @@ -1921,7 +1925,9 @@ JRESULT jd_decomp ( JDEC* jd, /* Initialized decompression object */ uint16_t (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ uint8_t scale, /* Output de-scaling factor (0 to 3) */ - void* display + void* display, + uint8_t dither, + uint8_t invert ) { uint16_t x, y, mx, my; @@ -1932,6 +1938,8 @@ JRESULT jd_decomp ( if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; jd->scale = scale; jd->_display = display; + jd->_dither = dither; + jd->_invert = invert; mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ diff --git a/tjpgd.h b/tjpgd.h index ed26f5b..29cd7f9 100644 --- a/tjpgd.h +++ b/tjpgd.h @@ -77,11 +77,13 @@ struct JDEC_s { void* device; /* Pointer to I/O device identifiler for the session */ uint8_t swap; /* Added by Bodmer to control byte swapping */ void* _display; + uint8_t _dither; + uint8_t _invert; }; /* TJpgDec API functions */ JRESULT jd_prepare (JDEC*, uint16_t(*)(JDEC*,uint8_t*,uint16_t), void*, uint16_t, void*); -JRESULT jd_decomp (JDEC*, uint16_t(*)(JDEC*,void*,JRECT*), uint8_t, void*); +JRESULT jd_decomp (JDEC*, uint16_t(*)(JDEC*,void*,JRECT*), uint8_t, void*, uint8_t, uint8_t); #ifdef __cplusplus