From b3d563040ab8b3f9edaa7192b83662a008411336 Mon Sep 17 00:00:00 2001 From: phranck Date: Sat, 14 Feb 2026 00:43:22 +0100 Subject: [PATCH] Feat: Add Image view with ASCII art rendering, bracketed paste, and input filtering - Add Image view rendering local files and URLs as colored ASCII art - Add CSTBImage C target wrapping stb_image for cross-platform image decoding - Add ASCIIConverter with block, ASCII, and braille character sets - Support trueColor, ANSI-256, grayscale, and mono color modes - Add Floyd-Steinberg dithering for improved visual quality - Add async image loading with URLImageCache for URL sources - Add bracketed paste mode for bulk text insertion in text fields - Add TextContentType modifier for input character filtering - Add ContentMode enum and aspectRatio(_:contentMode:) View modifier - Add text-input priority in key dispatch to prevent shortcut conflicts - Add Image (File) and Image (URL) demo pages to example app - Update DocC documentation with new symbols and layout table --- Package.resolved | 2 +- Package.swift | 12 +- Sources/CSTBImage/include/module.modulemap | 4 + Sources/CSTBImage/include/stb_image.h | 7988 +++++++++++++++++ Sources/CSTBImage/stb_image_impl.c | 6 + Sources/TUIkit/App/App.swift | 7 +- Sources/TUIkit/App/InputHandler.swift | 20 +- Sources/TUIkit/Core/KeyEvent.swift | 3 + Sources/TUIkit/Focus/Focus.swift | 8 + Sources/TUIkit/Focus/TextFieldHandler.swift | 41 +- Sources/TUIkit/Image/ASCIIConverter.swift | 498 + Sources/TUIkit/Image/ImageLoader.swift | 207 + Sources/TUIkit/Image/RGBAImage.swift | 209 + Sources/TUIkit/Rendering/Terminal.swift | 64 + Sources/TUIkit/Styling/ContentMode.swift | 38 + Sources/TUIkit/Styling/TextContentType.swift | 209 + .../TUIkit.docc/Articles/LayoutSystem.md | 1 + Sources/TUIkit/TUIkit.docc/TUIkit.md | 4 + Sources/TUIkit/Views/Image.swift | 265 + Sources/TUIkit/Views/SecureField.swift | 1 + Sources/TUIkit/Views/TextField.swift | 1 + Sources/TUIkit/Views/_ImageCore.swift | 258 + Sources/TUIkitExample/ContentView.swift | 68 +- .../TUIkitExample/Pages/ImageFilePage.swift | 93 + .../TUIkitExample/Pages/ImageURLPage.swift | 114 + .../TUIkitExample/Pages/MainMenuPage.swift | 2 + .../TUIkitExample/Resources/demo-image.jpg | Bin 0 -> 236628 bytes Tests/TUIkitTests/ImageTests.swift | 337 + 28 files changed, 10411 insertions(+), 49 deletions(-) create mode 100644 Sources/CSTBImage/include/module.modulemap create mode 100644 Sources/CSTBImage/include/stb_image.h create mode 100644 Sources/CSTBImage/stb_image_impl.c create mode 100644 Sources/TUIkit/Image/ASCIIConverter.swift create mode 100644 Sources/TUIkit/Image/ImageLoader.swift create mode 100644 Sources/TUIkit/Image/RGBAImage.swift create mode 100644 Sources/TUIkit/Styling/ContentMode.swift create mode 100644 Sources/TUIkit/Styling/TextContentType.swift create mode 100644 Sources/TUIkit/Views/Image.swift create mode 100644 Sources/TUIkit/Views/_ImageCore.swift create mode 100644 Sources/TUIkitExample/Pages/ImageFilePage.swift create mode 100644 Sources/TUIkitExample/Pages/ImageURLPage.swift create mode 100644 Sources/TUIkitExample/Resources/demo-image.jpg create mode 100644 Tests/TUIkitTests/ImageTests.swift diff --git a/Package.resolved b/Package.resolved index 80bb5e6d..8041ace4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "20a49b01b0e08a4eba6bdf90e830aa1c43301d8c17142c3e986cd34a9ed67d16", + "originHash" : "978155e2813f61182dd53d12465d831a26121bd7f15b38bea29cc7abc323457b", "pins" : [ { "identity" : "swift-docc-plugin", diff --git a/Package.swift b/Package.swift index d3a7ec73..b4096e8e 100644 --- a/Package.swift +++ b/Package.swift @@ -27,11 +27,19 @@ let package = Package( ], targets: [ .target( - name: "TUIkit" + name: "CSTBImage", + publicHeadersPath: "include" + ), + .target( + name: "TUIkit", + dependencies: ["CSTBImage"] ), .executableTarget( name: "TUIkitExample", - dependencies: ["TUIkit"] + dependencies: ["TUIkit"], + resources: [ + .copy("Resources"), + ] ), .testTarget( name: "TUIkitTests", diff --git a/Sources/CSTBImage/include/module.modulemap b/Sources/CSTBImage/include/module.modulemap new file mode 100644 index 00000000..1f77c578 --- /dev/null +++ b/Sources/CSTBImage/include/module.modulemap @@ -0,0 +1,4 @@ +module CSTBImage { + header "stb_image.h" + export * +} diff --git a/Sources/CSTBImage/include/stb_image.h b/Sources/CSTBImage/include/stb_image.h new file mode 100644 index 00000000..9eedabed --- /dev/null +++ b/Sources/CSTBImage/include/stb_image.h @@ -0,0 +1,7988 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 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, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/Sources/CSTBImage/stb_image_impl.c b/Sources/CSTBImage/stb_image_impl.c new file mode 100644 index 00000000..1fcdffa7 --- /dev/null +++ b/Sources/CSTBImage/stb_image_impl.c @@ -0,0 +1,6 @@ +// stb_image implementation file +// This activates the implementation of stb_image.h (single-header library). +// Public domain / MIT license - see stb_image.h for details. + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" diff --git a/Sources/TUIkit/App/App.swift b/Sources/TUIkit/App/App.swift index 1099db85..3d5ffc77 100644 --- a/Sources/TUIkit/App/App.swift +++ b/Sources/TUIkit/App/App.swift @@ -174,10 +174,11 @@ extension AppRunner { } // Read key events (non-blocking with VTIME=0) - // Process multiple events per frame to prevent input buffering lag, - // but limit to avoid render starvation and keep CPU usage low. + // Process all available events per frame. A high limit prevents + // input buffering lag during paste operations while still avoiding + // infinite loops if input arrives faster than we can process. var eventsProcessed = 0 - let maxEventsPerFrame = 5 + let maxEventsPerFrame = 128 while eventsProcessed < maxEventsPerFrame, let keyEvent = terminal.readKeyEvent() { inputHandler.handle(keyEvent) diff --git a/Sources/TUIkit/App/InputHandler.swift b/Sources/TUIkit/App/InputHandler.swift index 2725a43e..f5823289 100644 --- a/Sources/TUIkit/App/InputHandler.swift +++ b/Sources/TUIkit/App/InputHandler.swift @@ -42,6 +42,19 @@ extension InputHandler { /// /// - Parameter event: The key event to handle. func handle(_ event: KeyEvent) { + // Text-Input Priority: when a text-input element (TextField/SecureField) + // is focused, let it handle the event FIRST. This ensures printable + // characters, backspace, delete, arrows, home, end, and enter reach the + // text field before any other layer can intercept them. + // + // Only structural/navigation keys that the text field does NOT consume + // (Escape, Tab, unhandled Ctrl+shortcuts) fall through to other layers. + if focusManager.hasTextInputFocus { + if focusManager.dispatchKeyEvent(event) { + return + } + } + // Layer 1: Status bar items with actions if statusBar.handleKeyEvent(event) { return @@ -53,8 +66,11 @@ extension InputHandler { } // Layer 3: Focus system (Tab navigation, Enter/Space on focused buttons) - if focusManager.dispatchKeyEvent(event) { - return + // Skipped when text-input has focus since it was already dispatched above. + if !focusManager.hasTextInputFocus { + if focusManager.dispatchKeyEvent(event) { + return + } } // Layer 4: Default key bindings diff --git a/Sources/TUIkit/Core/KeyEvent.swift b/Sources/TUIkit/Core/KeyEvent.swift index 95271604..78a828cd 100644 --- a/Sources/TUIkit/Core/KeyEvent.swift +++ b/Sources/TUIkit/Core/KeyEvent.swift @@ -69,6 +69,9 @@ public enum Key: Hashable, Sendable { // Character key case character(Character) + // Bracketed paste (bulk text from terminal paste operation) + case paste(String) + /// Creates a Key from a character if it's a simple character. public static func from(_ char: Character) -> Self { .character(char) diff --git a/Sources/TUIkit/Focus/Focus.swift b/Sources/TUIkit/Focus/Focus.swift index 4471eadc..4e6824c8 100644 --- a/Sources/TUIkit/Focus/Focus.swift +++ b/Sources/TUIkit/Focus/Focus.swift @@ -141,6 +141,14 @@ public final class FocusManager: @unchecked Sendable { public var currentFocusedID: String? { focusedID } + + /// Whether the currently focused element is a text-input handler. + /// + /// When `true`, the input handler should give the focused element + /// priority for key events before dispatching to other layers. + var hasTextInputFocus: Bool { + currentFocused is TextFieldHandler + } } // MARK: - Public API diff --git a/Sources/TUIkit/Focus/TextFieldHandler.swift b/Sources/TUIkit/Focus/TextFieldHandler.swift index 817f62e1..2a6d5ee6 100644 --- a/Sources/TUIkit/Focus/TextFieldHandler.swift +++ b/Sources/TUIkit/Focus/TextFieldHandler.swift @@ -74,6 +74,13 @@ final class TextFieldHandler: Focusable { /// Callback triggered when the user presses Enter. var onSubmit: (() -> Void)? + /// The text content type used for input character filtering. + /// + /// When set, both typed characters and pasted text are filtered against + /// the allowed character set of the content type. Synced from the + /// environment during each render pass. + var textContentType: TextContentType? + /// Undo history stack storing previous text states and cursor positions. private var undoStack: [(text: String, cursor: Int)] = [] @@ -292,6 +299,10 @@ extension TextFieldHandler { onSubmit?() return true + case .paste(let text): + insertText(text) + return true + default: return false } @@ -307,6 +318,8 @@ extension TextFieldHandler { /// /// - Parameter char: The character to insert. func insertCharacter(_ char: Character) { + guard textContentType?.isAllowed(char) ?? true else { return } + pushUndoState() // Replace selection if present @@ -466,7 +479,27 @@ extension TextFieldHandler { /// Uses `pbpaste` on macOS. Replaces selection if any. func paste() { guard let pastedText = pasteFromClipboard() else { return } - guard !pastedText.isEmpty else { return } + insertText(pastedText) + } + + /// Inserts a string at the cursor position in a single operation. + /// + /// Used by both clipboard paste (`Ctrl+V`) and bracketed paste + /// (terminal paste via `Cmd+V`). Replaces selection if any. + /// + /// - Parameter string: The text to insert. + func insertText(_ string: String) { + guard !string.isEmpty else { return } + + // For single-line text fields, strip newlines from pasted text. + var sanitized = string.replacingOccurrences(of: "\n", with: "") + .replacingOccurrences(of: "\r", with: "") + + // Filter by content type if set. + if let contentType = textContentType { + sanitized = contentType.filterString(sanitized) + } + guard !sanitized.isEmpty else { return } pushUndoState() @@ -476,12 +509,12 @@ extension TextFieldHandler { clearSelection() } - // Insert pasted text + // Insert text var current = text.wrappedValue let index = current.index(current.startIndex, offsetBy: min(cursorPosition, current.count)) - current.insert(contentsOf: pastedText, at: index) + current.insert(contentsOf: sanitized, at: index) text.wrappedValue = current - cursorPosition += pastedText.count + cursorPosition += sanitized.count } } diff --git a/Sources/TUIkit/Image/ASCIIConverter.swift b/Sources/TUIkit/Image/ASCIIConverter.swift new file mode 100644 index 00000000..433aa984 --- /dev/null +++ b/Sources/TUIkit/Image/ASCIIConverter.swift @@ -0,0 +1,498 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// ASCIIConverter.swift +// +// Created by LAYERED.work +// License: MIT + +// MARK: - Character Set + +/// The set of characters used for ASCII art rendering. +/// +/// Each set trades off between compatibility and visual quality. +public enum ASCIICharacterSet: Sendable, Equatable { + /// Standard ASCII characters (10 levels). Works in every terminal. + case ascii + + /// Unicode block elements (5 levels). Requires Unicode support. + case blocks + + /// Unicode Braille patterns (2x4 pixel cells, 256 patterns). Highest resolution. + case braille +} + +// MARK: - Color Mode + +/// Controls how colors are rendered in ASCII art output. +public enum ASCIIColorMode: Sendable, Equatable { + /// 24-bit RGB using `\e[38;2;R;G;B` sequences. Best quality. + case trueColor + + /// 256-color ANSI palette. Good terminal compatibility. + case ansi256 + + /// 24 shades of gray. + case grayscale + + /// Black and white only. Universal compatibility. + case mono +} + +// MARK: - Dithering Mode + +/// The dithering algorithm applied during color quantization. +public enum DitheringMode: Sendable, Equatable { + /// Floyd-Steinberg error diffusion. Good for smooth gradients. + case floydSteinberg + + /// No dithering. Fastest. + case none +} + +// MARK: - ASCII Converter + +/// Converts an `RGBAImage` to colored ASCII art strings. +/// +/// The conversion pipeline: +/// 1. Scale image to target character dimensions +/// 2. Apply aspect ratio correction (terminal chars are ~2:1) +/// 3. Optionally apply dithering +/// 4. Map each pixel to a character based on luminance +/// 5. Colorize each character using the selected color mode +struct ASCIIConverter: Sendable { + + /// The character set to use for brightness mapping. + let characterSet: ASCIICharacterSet + + /// The color mode for output. + let colorMode: ASCIIColorMode + + /// The dithering algorithm (nil or .none means no dithering). + let dithering: DitheringMode + + /// Creates a converter with the specified options. + init( + characterSet: ASCIICharacterSet = .blocks, + colorMode: ASCIIColorMode = .trueColor, + dithering: DitheringMode = .none + ) { + self.characterSet = characterSet + self.colorMode = colorMode + self.dithering = dithering + } +} + +// MARK: - Conversion + +extension ASCIIConverter { + + /// Converts an image to an array of ANSI-colored strings (one per row). + /// + /// - Parameters: + /// - image: The source image. + /// - width: Target width in characters. + /// - height: Target height in characters. + /// - Returns: An array of ANSI-formatted strings representing the ASCII art. + func convert(_ image: RGBAImage, width: Int, height: Int) -> [String] { + guard image.width > 0, image.height > 0, width > 0, height > 0 else { + return [] + } + + // For braille, each character cell covers 2x4 pixels. + let pixelWidth: Int + let pixelHeight: Int + if characterSet == .braille { + pixelWidth = width * 2 + pixelHeight = height * 4 + } else { + pixelWidth = width + pixelHeight = height + } + + // Scale image to target pixel dimensions + var scaled = image.scaledBilinear(to: pixelWidth, pixelHeight) + + // Apply dithering if requested (only meaningful for non-trueColor modes) + if dithering == .floydSteinberg, colorMode != .trueColor { + scaled = applyFloydSteinbergDithering(scaled) + } + + // Convert to ASCII lines + if characterSet == .braille { + return convertBraille(scaled, width: width, height: height) + } + + return convertCharacterBased(scaled, width: width, height: height) + } +} + +// MARK: - Character-Based Conversion + +extension ASCIIConverter { + + /// Converts using character brightness mapping (ascii, blocks). + private func convertCharacterBased(_ image: RGBAImage, width: Int, height: Int) -> [String] { + let ramp = characterRamp + + var lines = [String]() + lines.reserveCapacity(height) + + for y in 0.. [String] { + // Braille dot positions (column, row) -> bit index + // ⠁ = bit 0 (0,0) ⠈ = bit 3 (1,0) + // ⠂ = bit 1 (0,1) ⠐ = bit 4 (1,1) + // ⠄ = bit 2 (0,2) ⠠ = bit 5 (1,2) + // ⡀ = bit 6 (0,3) ⢀ = bit 7 (1,3) + let dotBits: [[Int]] = [ + [0, 3], // row 0: left=bit0, right=bit3 + [1, 4], // row 1: left=bit1, right=bit4 + [2, 5], // row 2: left=bit2, right=bit5 + [6, 7], // row 3: left=bit6, right=bit7 + ] + + let threshold = 128.0 + var lines = [String]() + lines.reserveCapacity(height) + + for charY in 0..= threshold { + pattern |= 1 << dotBits[dy][dx] + } + } + } + + // Braille character: U+2800 + pattern + let brailleChar = Character(Unicode.Scalar(0x2800 + UInt32(pattern))!) + + // Average color for this cell + let avgPixel: RGBA + if count > 0 { + avgPixel = RGBA( + r: UInt8(clamping: totalR / count), + g: UInt8(clamping: totalG / count), + b: UInt8(clamping: totalB / count) + ) + } else { + avgPixel = RGBA(r: 0, g: 0, b: 0) + } + + let colorCode = foregroundColorCode(for: avgPixel) + if colorCode != lastColor { + if !lastColor.isEmpty { + line += ANSIRenderer.reset + } + line += colorCode + lastColor = colorCode + } + line.append(brailleChar) + } + + if !lastColor.isEmpty { + line += ANSIRenderer.reset + } + lines.append(line) + } + + return lines + } +} + +// MARK: - Color Output + +extension ASCIIConverter { + + /// Returns the ANSI foreground color escape code for a pixel. + private func foregroundColorCode(for pixel: RGBA) -> String { + switch colorMode { + case .trueColor: + return "\(ANSIRenderer.csi)38;2;\(pixel.r);\(pixel.g);\(pixel.b)m" + + case .ansi256: + let index = quantizeToANSI256(pixel) + return "\(ANSIRenderer.csi)38;5;\(index)m" + + case .grayscale: + let gray = Int(pixel.luminance / 255.0 * 23.0) + let index = 232 + min(max(gray, 0), 23) + return "\(ANSIRenderer.csi)38;5;\(index)m" + + case .mono: + return "" + } + } + + /// Quantizes an RGB pixel to the nearest ANSI 256-color index. + private func quantizeToANSI256(_ pixel: RGBA) -> UInt8 { + // Check for near-grayscale + let rDiff = abs(Int(pixel.r) - Int(pixel.g)) + let gDiff = abs(Int(pixel.g) - Int(pixel.b)) + if rDiff < 10, gDiff < 10 { + let gray = Int(pixel.r) + if gray < 8 { return 16 } + if gray > 248 { return 231 } + return UInt8(232 + (gray - 8) / 10) + } + + // 6x6x6 color cube (indices 16-231) + let r = Int((Double(pixel.r) / 255.0 * 5.0).rounded()) + let g = Int((Double(pixel.g) / 255.0 * 5.0).rounded()) + let b = Int((Double(pixel.b) / 255.0 * 5.0).rounded()) + return UInt8(16 + 36 * r + 6 * g + b) + } +} + +// MARK: - Floyd-Steinberg Dithering + +extension ASCIIConverter { + + /// Applies Floyd-Steinberg error diffusion dithering. + /// + /// Distributes quantization error to neighboring pixels: + /// - Right: 7/16 + /// - Bottom-left: 3/16 + /// - Bottom: 5/16 + /// - Bottom-right: 1/16 + private func applyFloydSteinbergDithering(_ image: RGBAImage) -> RGBAImage { + var result = image + + for y in 0.. 0 { + result.addError(at: x - 1, y + 1, + rError: rErr * 3.0 / 16.0, + gError: gErr * 3.0 / 16.0, + bError: bErr * 3.0 / 16.0) + } + result.addError(at: x, y + 1, + rError: rErr * 5.0 / 16.0, + gError: gErr * 5.0 / 16.0, + bError: bErr * 5.0 / 16.0) + if x + 1 < image.width { + result.addError(at: x + 1, y + 1, + rError: rErr * 1.0 / 16.0, + gError: gErr * 1.0 / 16.0, + bError: bErr * 1.0 / 16.0) + } + } + } + } + + return result + } + + /// Quantizes a pixel to its nearest representative value for the current color mode. + private func quantizePixel(_ pixel: RGBA) -> RGBA { + switch colorMode { + case .trueColor: + return pixel + + case .ansi256: + let index = quantizeToANSI256(pixel) + return ansi256ToRGB(index) + + case .grayscale: + let gray = UInt8(clamping: Int(pixel.luminance)) + return RGBA(r: gray, g: gray, b: gray) + + case .mono: + let val: UInt8 = pixel.luminance > 128.0 ? 255 : 0 + return RGBA(r: val, g: val, b: val) + } + } + + /// Converts an ANSI 256-color index back to approximate RGB. + private func ansi256ToRGB(_ index: UInt8) -> RGBA { + let idx = Int(index) + if idx < 16 { + // Standard colors (approximate) + let table: [(UInt8, UInt8, UInt8)] = [ + (0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0), + (0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192), + (128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0), + (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255), + ] + let (r, g, b) = table[idx] + return RGBA(r: r, g: g, b: b) + } else if idx < 232 { + // 6x6x6 color cube + let offset = idx - 16 + let r = offset / 36 + let g = (offset % 36) / 6 + let b = offset % 6 + return RGBA( + r: r == 0 ? 0 : UInt8(55 + r * 40), + g: g == 0 ? 0 : UInt8(55 + g * 40), + b: b == 0 ? 0 : UInt8(55 + b * 40) + ) + } else { + // Grayscale ramp + let gray = UInt8(8 + (idx - 232) * 10) + return RGBA(r: gray, g: gray, b: gray) + } + } +} + +// MARK: - Aspect Ratio + +extension ASCIIConverter { + + /// Calculates the target character dimensions preserving aspect ratio. + /// + /// Terminal characters are approximately 2:1 (height:width), so the + /// vertical dimension is halved to compensate. + /// + /// - Parameters: + /// - imageWidth: Source image width in pixels. + /// - imageHeight: Source image height in pixels. + /// - maxWidth: Maximum width in characters. + /// - maxHeight: Maximum height in characters (optional). + /// - contentMode: Whether to fit within or fill the available bounds. + /// - overrideAspectRatio: An explicit width/height ratio. When `nil`, + /// the source image's natural ratio is used. + /// - Returns: The target width and height in characters. + static func targetSize( + imageWidth: Int, + imageHeight: Int, + maxWidth: Int, + maxHeight: Int? = nil, + contentMode: ContentMode = .fit, + overrideAspectRatio: Double? = nil + ) -> (width: Int, height: Int) { + let terminalAspect = 2.0 // Terminal chars are ~2x taller than wide + + // Use override ratio or compute from source dimensions. + let sourceRatio = overrideAspectRatio + ?? (Double(imageWidth) / Double(imageHeight)) + + // correctedRatio accounts for terminal character aspect (tall cells). + let correctedRatio = sourceRatio * terminalAspect + + let maxH = maxHeight ?? Int((Double(maxWidth) / correctedRatio).rounded()) + + let targetWidth: Int + let targetHeight: Int + + switch contentMode { + case .fit: + // Scale to fit within both bounds. Result <= bounds. + let widthFromHeight = Int((Double(maxH) * correctedRatio).rounded()) + if widthFromHeight <= maxWidth { + targetWidth = widthFromHeight + targetHeight = maxH + } else { + targetWidth = maxWidth + targetHeight = Int((Double(maxWidth) / correctedRatio).rounded()) + } + + case .fill: + // Scale so the shorter dimension fills its bound. + // Result may exceed one bound. + let widthFromHeight = Int((Double(maxH) * correctedRatio).rounded()) + if widthFromHeight >= maxWidth { + targetWidth = widthFromHeight + targetHeight = maxH + } else { + targetWidth = maxWidth + targetHeight = Int((Double(maxWidth) / correctedRatio).rounded()) + } + } + + return (width: max(1, targetWidth), height: max(1, targetHeight)) + } +} diff --git a/Sources/TUIkit/Image/ImageLoader.swift b/Sources/TUIkit/Image/ImageLoader.swift new file mode 100644 index 00000000..8ad50d44 --- /dev/null +++ b/Sources/TUIkit/Image/ImageLoader.swift @@ -0,0 +1,207 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// ImageLoader.swift +// +// Created by LAYERED.work +// License: MIT + +import CSTBImage +import Foundation + +// MARK: - ImageLoader Protocol + +/// Loads images from file paths or raw data and converts them to `RGBAImage`. +/// +/// Uses stb_image (bundled C library) on all platforms for consistent behavior. +/// Supported formats: PNG, JPEG, GIF, BMP, TGA, HDR, PSD, PNM. +protocol ImageLoader: Sendable { + /// Loads an image from a file path. + /// + /// - Parameter path: The absolute file path to the image. + /// - Returns: The decoded image as `RGBAImage`. + /// - Throws: `ImageLoadError` if the file cannot be read or decoded. + func loadImage(from path: String) throws -> RGBAImage + + /// Loads an image from raw data. + /// + /// - Parameter data: The image file data. + /// - Returns: The decoded image as `RGBAImage`. + /// - Throws: `ImageLoadError` if the data cannot be decoded. + func loadImage(from data: Data) throws -> RGBAImage +} + +// MARK: - ImageLoadError + +/// Errors that can occur during image loading. +enum ImageLoadError: Error, LocalizedError, CustomStringConvertible { + /// The file was not found at the given path. + case fileNotFound(String) + + /// The image format is not supported. + case unsupportedFormat(String) + + /// The image data could not be decoded. + case decodingFailed(String) + + /// A URL download failed. + case downloadFailed(String) + + var description: String { + switch self { + case .fileNotFound(let path): + return "Image file not found: \(path)" + case .unsupportedFormat(let format): + return "Unsupported image format: \(format)" + case .decodingFailed(let reason): + return "Image decoding failed: \(reason)" + case .downloadFailed(let reason): + return "Image download failed: \(reason)" + } + } + + var errorDescription: String? { description } +} + +// MARK: - Platform Image Loader + +/// Cross-platform image loader using stb_image. +/// +/// Supports PNG, JPEG, GIF, BMP, TGA, HDR, PSD, and PNM formats +/// on both macOS and Linux. stb_image is a public-domain single-header +/// C library bundled as a local `CSTBImage` target. +struct PlatformImageLoader: ImageLoader { + + func loadImage(from path: String) throws -> RGBAImage { + guard FileManager.default.fileExists(atPath: path) else { + throw ImageLoadError.fileNotFound(path) + } + + var width: Int32 = 0 + var height: Int32 = 0 + var channels: Int32 = 0 + + guard let rawPixels = stbi_load(path, &width, &height, &channels, 4) else { + let reason = String(cString: stbi_failure_reason()) + throw ImageLoadError.decodingFailed("stb_image: \(reason)") + } + defer { stbi_image_free(rawPixels) } + + return pixelsFromRaw(rawPixels, width: Int(width), height: Int(height)) + } + + func loadImage(from data: Data) throws -> RGBAImage { + var width: Int32 = 0 + var height: Int32 = 0 + var channels: Int32 = 0 + + let rawPixels: UnsafeMutablePointer? = data.withUnsafeBytes { buffer in + guard let baseAddress = buffer.baseAddress else { return nil } + return stbi_load_from_memory( + baseAddress.assumingMemoryBound(to: UInt8.self), + Int32(data.count), + &width, + &height, + &channels, + 4 + ) + } + + guard let pixels = rawPixels else { + let reason = String(cString: stbi_failure_reason()) + throw ImageLoadError.decodingFailed("stb_image: \(reason)") + } + defer { stbi_image_free(pixels) } + + return pixelsFromRaw(pixels, width: Int(width), height: Int(height)) + } +} + +// MARK: - Private Helpers + +extension PlatformImageLoader { + + /// Converts raw stb_image RGBA output to an `RGBAImage`. + private func pixelsFromRaw( + _ rawPixels: UnsafeMutablePointer, + width: Int, + height: Int + ) -> RGBAImage { + let count = width * height + var pixels = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: count) + + for i in 0.. RGBAImage? { + lock.lock() + defer { lock.unlock() } + return cache[urlString] + } + + /// Stores an image in the cache for the given URL string. + func set(_ urlString: String, image: RGBAImage) { + lock.lock() + defer { lock.unlock() } + cache[urlString] = image + } +} + +// MARK: - URL Image Loading + +extension PlatformImageLoader { + + /// Loads an image from a URL, using the session cache. + /// + /// On first access the image is downloaded synchronously and cached. + /// Subsequent calls for the same URL return the cached copy. + /// + /// - Parameter urlString: The URL to download. + /// - Returns: The decoded image. + /// - Throws: `ImageLoadError` on network or decoding failure. + func loadImage(from urlString: String, cache: URLImageCache = .shared) throws -> RGBAImage { + if let cached = cache.get(urlString) { + return cached + } + + guard let url = URL(string: urlString) else { + throw ImageLoadError.downloadFailed("Invalid URL: \(urlString)") + } + + let data: Data + do { + data = try Data(contentsOf: url) + } catch { + throw ImageLoadError.downloadFailed(error.localizedDescription) + } + + let image = try loadImage(from: data) + cache.set(urlString, image: image) + return image + } +} diff --git a/Sources/TUIkit/Image/RGBAImage.swift b/Sources/TUIkit/Image/RGBAImage.swift new file mode 100644 index 00000000..688c96b6 --- /dev/null +++ b/Sources/TUIkit/Image/RGBAImage.swift @@ -0,0 +1,209 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// RGBAImage.swift +// +// Created by LAYERED.work +// License: MIT + +// MARK: - RGBA Pixel + +/// A single pixel with red, green, blue, and alpha channels. +/// +/// Used as the intermediate representation for image data before +/// ASCII art conversion. Each channel is stored as a `UInt8` (0-255). +struct RGBA: Sendable, Equatable { + var r: UInt8 + var g: UInt8 + var b: UInt8 + var a: UInt8 + + /// Creates an opaque pixel with the given RGB values. + init(r: UInt8, g: UInt8, b: UInt8, a: UInt8 = 255) { + self.r = r + self.g = g + self.b = b + self.a = a + } +} + +// MARK: - Luminance + +extension RGBA { + + /// The perceived luminance using ITU-R BT.601 coefficients. + /// + /// Returns a value in the range 0.0 (black) to 255.0 (white). + var luminance: Double { + Double(r) * 0.299 + Double(g) * 0.587 + Double(b) * 0.114 + } +} + +// MARK: - RGBAImage + +/// A raw image stored as a flat array of RGBA pixels in row-major order. +/// +/// This is the platform-independent representation produced by +/// `ImageLoader` implementations and consumed by `ASCIIConverter`. +struct RGBAImage: Sendable { + /// Image width in pixels. + let width: Int + + /// Image height in pixels. + let height: Int + + /// Row-major pixel data (`width * height` elements). + private(set) var pixels: [RGBA] + + /// Creates an image from dimensions and pixel data. + /// + /// - Parameters: + /// - width: Image width in pixels. + /// - height: Image height in pixels. + /// - pixels: Pixel data in row-major order. Must contain `width * height` elements. + init(width: Int, height: Int, pixels: [RGBA]) { + precondition(pixels.count == width * height, "Pixel count must match width * height") + self.width = width + self.height = height + self.pixels = pixels + } +} + +// MARK: - Pixel Access + +extension RGBAImage { + + /// Returns the pixel at the given coordinates. + /// + /// - Parameters: + /// - x: Column (0-based, left to right). + /// - y: Row (0-based, top to bottom). + /// - Returns: The RGBA pixel value. + func pixel(at x: Int, _ y: Int) -> RGBA { + pixels[y * width + x] + } + + /// Sets the pixel at the given coordinates. + /// + /// - Parameters: + /// - x: Column (0-based). + /// - y: Row (0-based). + /// - value: The new pixel value. + mutating func setPixel(at x: Int, _ y: Int, value: RGBA) { + pixels[y * width + x] = value + } + + /// Adds an error value to the pixel at the given coordinates (for dithering). + /// + /// Clamps each channel to the valid 0-255 range. + /// + /// - Parameters: + /// - x: Column. + /// - y: Row. + /// - rError: Red channel error. + /// - gError: Green channel error. + /// - bError: Blue channel error. + mutating func addError(at x: Int, _ y: Int, rError: Double, gError: Double, bError: Double) { + let index = y * width + x + let p = pixels[index] + pixels[index] = RGBA( + r: UInt8(clamping: Int(Double(p.r) + rError)), + g: UInt8(clamping: Int(Double(p.g) + gError)), + b: UInt8(clamping: Int(Double(p.b) + bError)) + ) + } +} + +// MARK: - Image Scaling + +extension RGBAImage { + + /// Returns a scaled copy using nearest-neighbor interpolation. + /// + /// - Parameters: + /// - targetWidth: The desired width. + /// - targetHeight: The desired height. + /// - Returns: A new image with the specified dimensions. + func scaled(to targetWidth: Int, _ targetHeight: Int) -> RGBAImage { + guard targetWidth > 0, targetHeight > 0 else { + return RGBAImage(width: 0, height: 0, pixels: []) + } + + var result = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: targetWidth * targetHeight) + + for y in 0.. RGBAImage { + guard targetWidth > 0, targetHeight > 0 else { + return RGBAImage(width: 0, height: 0, pixels: []) + } + + var result = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: targetWidth * targetHeight) + let xRatio = Double(width) / Double(targetWidth) + let yRatio = Double(height) / Double(targetHeight) + + for y in 0.. Double { + let top = v00 * (1.0 - xFrac) + v10 * xFrac + let bottom = v01 * (1.0 - xFrac) + v11 * xFrac + return top * (1.0 - yFrac) + bottom * yFrac + } +} diff --git a/Sources/TUIkit/Rendering/Terminal.swift b/Sources/TUIkit/Rendering/Terminal.swift index 4cae6690..43ae855b 100644 --- a/Sources/TUIkit/Rendering/Terminal.swift +++ b/Sources/TUIkit/Rendering/Terminal.swift @@ -138,11 +138,21 @@ extension Terminal { tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) isRawMode = true + + // Enable bracketed paste mode so that terminal paste operations + // are wrapped in ESC[200~ ... ESC[201~ markers. This allows the + // application to detect pasted text and insert it as a single + // bulk operation instead of processing each character individually. + writeImmediate("\u{1B}[?2004h") } /// Disables raw mode and restores normal terminal operation. func disableRawMode() { guard isRawMode, var original = originalTermios else { return } + + // Disable bracketed paste mode before restoring terminal state. + writeImmediate("\u{1B}[?2004l") + tcsetattr(STDIN_FILENO, TCSAFLUSH, &original) isRawMode = false } @@ -270,12 +280,66 @@ extension Terminal { /// Reads a key event from the terminal. /// + /// When bracketed paste mode is active the terminal wraps pasted text + /// in `ESC[200~` ... `ESC[201~` markers. This method detects the start + /// marker, buffers all bytes until the end marker, and returns the + /// entire pasted text as a single `Key.paste(String)` event. + /// /// - Returns: The key event, or nil on timeout/error. func readKeyEvent() -> KeyEvent? { let bytes = readBytes() guard !bytes.isEmpty else { return nil } + + // Detect bracketed paste start: ESC [ 2 0 0 ~ + if bytes == [0x1B, 0x5B, 0x32, 0x30, 0x30, 0x7E] { + let pastedText = readBracketedPasteContent() + return KeyEvent(key: .paste(pastedText)) + } + return KeyEvent.parse(bytes) } + + /// Reads bytes until the bracketed paste end marker `ESC[201~` is found. + /// + /// Called after the paste start marker `ESC[200~` has been detected. + /// Reads byte-by-byte, watching for the 6-byte end sequence. All bytes + /// before the end marker are collected and returned as a UTF-8 string. + /// + /// - Returns: The pasted text content. + private func readBracketedPasteContent() -> String { + var content: [UInt8] = [] + // The end marker is: ESC [ 2 0 1 ~ + let endMarker: [UInt8] = [0x1B, 0x5B, 0x32, 0x30, 0x31, 0x7E] + + // Safety limit to prevent infinite buffering on malformed input. + let maxPasteBytes = 65_536 + + while content.count < maxPasteBytes { + var byte = [UInt8](repeating: 0, count: 1) + let bytesRead = read(STDIN_FILENO, &byte, 1) + guard bytesRead > 0 else { + // No more data available right now. For non-blocking reads + // (VMIN=0, VTIME=0) this means the paste end marker has not + // yet arrived. Wait briefly and retry. + usleep(1_000) // 1ms + continue + } + + content.append(byte[0]) + + // Check if content ends with the paste end marker. + if content.count >= endMarker.count { + let tail = Array(content.suffix(endMarker.count)) + if tail == endMarker { + // Remove the end marker from the content. + content.removeLast(endMarker.count) + break + } + } + } + + return String(bytes: content, encoding: .utf8) ?? String(content.map { Character(UnicodeScalar($0)) }) + } } // MARK: - Private Helpers diff --git a/Sources/TUIkit/Styling/ContentMode.swift b/Sources/TUIkit/Styling/ContentMode.swift new file mode 100644 index 00000000..e2a814f6 --- /dev/null +++ b/Sources/TUIkit/Styling/ContentMode.swift @@ -0,0 +1,38 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// ContentMode.swift +// +// Created by LAYERED.work +// License: MIT + +import Foundation + +// MARK: - ContentMode + +/// Constants that define how a view's content fills the available space. +/// +/// Use `ContentMode` with the ``View/aspectRatio(_:contentMode:)`` modifier +/// to control how an image or other content is scaled within its bounds. +/// +/// - ``fit``: Scales content to fit within the bounds while preserving +/// the aspect ratio. The content may not fill the entire available space. +/// - ``fill``: Scales content to fill the bounds while preserving +/// the aspect ratio. The content may extend beyond the available space. +/// +/// ## Usage +/// +/// ```swift +/// Image(.file("photo.png")) +/// .aspectRatio(contentMode: .fit) +/// +/// Image(.url("https://example.com/photo.png")) +/// .aspectRatio(16.0/9.0, contentMode: .fill) +/// ``` +public enum ContentMode: Sendable, Equatable { + /// Scales content to fit within the parent by maintaining the + /// aspect ratio. The resulting dimensions are always within bounds. + case fit + + /// Scales content to fill the parent by maintaining the aspect ratio. + /// The content may extend beyond the bounds along one dimension. + case fill +} diff --git a/Sources/TUIkit/Styling/TextContentType.swift b/Sources/TUIkit/Styling/TextContentType.swift new file mode 100644 index 00000000..8ab4d7d0 --- /dev/null +++ b/Sources/TUIkit/Styling/TextContentType.swift @@ -0,0 +1,209 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// TextContentType.swift +// +// Created by LAYERED.work +// License: MIT + +import Foundation + +// MARK: - TextContentType + +/// Declares the semantic content type of a text field and filters input accordingly. +/// +/// In SwiftUI, `UITextContentType` provides autofill hints to the system. +/// In a TUI there is no autofill, so `TextContentType` instead defines an +/// **allowed character set** for input filtering. Both typed characters and +/// pasted text are filtered against the allowed set; invalid characters are +/// silently dropped. +/// +/// ## Usage +/// +/// ```swift +/// // Only allow URL-valid characters +/// TextField("URL", text: $url) +/// .textContentType(.url) +/// +/// // Only allow digits +/// TextField("Code", text: $code) +/// .textContentType(.oneTimeCode) +/// +/// // Apply to all fields in a container +/// VStack { +/// TextField("User", text: $user) +/// SecureField("Password", text: $pass) +/// } +/// .textContentType(.username) +/// ``` +/// +/// ## Character Filtering +/// +/// | Type | Allowed Characters | +/// |-------------------|-----------------------------------------------| +/// | `url` | Alphanumeric, `:/.?#[]@!$&'()*+,;=-_~%` | +/// | `emailAddress` | Alphanumeric, `@._+-` | +/// | `telephoneNumber` | `0-9`, `+()-. #*`, space | +/// | `username` | Alphanumeric, `._-@` | +/// | `password` | All characters (no filtering) | +/// | `oneTimeCode` | `0-9` | +/// | `integer` | `0-9`, `-` | +/// | `decimal` | `0-9`, `-.` | +public enum TextContentType: Sendable, Equatable { + /// URL input. Allows alphanumeric characters and URL-safe punctuation. + case url + + /// Email address input. Allows alphanumeric characters, `@`, `.`, `_`, `+`, `-`. + case emailAddress + + /// Telephone number input. Allows digits, `+`, `(`, `)`, `-`, `.`, `#`, `*`, space. + case telephoneNumber + + /// Username input. Allows alphanumeric characters, `.`, `_`, `-`, `@`. + case username + + /// Password input. Allows all characters (no filtering). + case password + + /// One-time code input. Allows digits only. + case oneTimeCode + + /// Integer input. Allows digits and `-` for negative numbers. + case integer + + /// Decimal number input. Allows digits, `-`, and `.` for fractional numbers. + case decimal +} + +// MARK: - Character Filtering + +extension TextContentType { + /// The set of Unicode scalars allowed for this content type. + /// + /// `.password` returns `nil` to indicate no filtering. + var allowedCharacters: CharacterSet? { + switch self { + case .url: + return Self.urlCharacters + case .emailAddress: + return Self.emailCharacters + case .telephoneNumber: + return Self.phoneCharacters + case .username: + return Self.usernameCharacters + case .password: + return nil + case .oneTimeCode: + return Self.digitCharacters + case .integer: + return Self.integerCharacters + case .decimal: + return Self.decimalCharacters + } + } + + /// Whether the given character is allowed by this content type. + /// + /// - Parameter character: The character to check. + /// - Returns: `true` if the character passes the filter, or if this type + /// has no filter (`.password`). + func isAllowed(_ character: Character) -> Bool { + guard let allowed = allowedCharacters else { return true } + return character.unicodeScalars.allSatisfy { allowed.contains($0) } + } + + /// Filters a string, keeping only characters allowed by this content type. + /// + /// - Parameter string: The input string to filter. + /// - Returns: A new string containing only the allowed characters. + func filterString(_ string: String) -> String { + guard allowedCharacters != nil else { return string } + return String(string.filter { isAllowed($0) }) + } +} + +// MARK: - Character Set Definitions + +private extension TextContentType { + /// URL-safe characters per RFC 3986. + static let urlCharacters: CharacterSet = { + var set = CharacterSet.alphanumerics + set.insert(charactersIn: ":/.?#[]@!$&'()*+,;=-_~%") + return set + }() + + /// Email address characters. + static let emailCharacters: CharacterSet = { + var set = CharacterSet.alphanumerics + set.insert(charactersIn: "@._+-") + return set + }() + + /// Telephone number characters. + static let phoneCharacters: CharacterSet = { + var set = CharacterSet(charactersIn: "0123456789") + set.insert(charactersIn: "+()-. #*") + set.insert(charactersIn: " ") + return set + }() + + /// Username characters. + static let usernameCharacters: CharacterSet = { + var set = CharacterSet.alphanumerics + set.insert(charactersIn: "._-@") + return set + }() + + /// Digits only. + static let digitCharacters = CharacterSet(charactersIn: "0123456789") + + /// Integer characters (digits and minus sign). + static let integerCharacters = CharacterSet(charactersIn: "0123456789-") + + /// Decimal characters (digits, minus sign, and decimal point). + static let decimalCharacters = CharacterSet(charactersIn: "0123456789-.") +} + +// MARK: - Environment Key + +/// Environment key for the text content type. +private struct TextContentTypeKey: EnvironmentKey { + static let defaultValue: TextContentType? = nil +} + +extension EnvironmentValues { + /// The text content type for text fields. + /// + /// When set, text fields filter both typed characters and pasted text + /// against the allowed character set of the content type. + /// + /// Set this value using the `.textContentType(_:)` modifier: + /// + /// ```swift + /// TextField("URL", text: $url) + /// .textContentType(.url) + /// ``` + public var textContentType: TextContentType? { + get { self[TextContentTypeKey.self] } + set { self[TextContentTypeKey.self] = newValue } + } +} + +// MARK: - View Extension + +extension View { + /// Sets the text content type for text fields within this view. + /// + /// When a content type is set, both typed characters and pasted text + /// are filtered against the allowed character set. Invalid characters + /// are silently dropped. + /// + /// ```swift + /// TextField("URL", text: $url) + /// .textContentType(.url) + /// ``` + /// + /// - Parameter type: The content type, or `nil` to disable filtering. + /// - Returns: A view with the content type applied. + public func textContentType(_ type: TextContentType?) -> some View { + environment(\.textContentType, type) + } +} diff --git a/Sources/TUIkit/TUIkit.docc/Articles/LayoutSystem.md b/Sources/TUIkit/TUIkit.docc/Articles/LayoutSystem.md index 63fc7f99..32384ea0 100644 --- a/Sources/TUIkit/TUIkit.docc/Articles/LayoutSystem.md +++ b/Sources/TUIkit/TUIkit.docc/Articles/LayoutSystem.md @@ -117,6 +117,7 @@ Flexible children share remaining space equally. If multiple spacers exist, they | ``Slider`` | Yes | Width-flexible | | ``Divider`` | Yes | Width-flexible | | ``ProgressView`` | Yes | Width-flexible | +| ``Image`` | Yes | Both-flexible (fills available space) | Views that are not `Layoutable` use the default implementation which renders first, then reports the buffer size as fixed. diff --git a/Sources/TUIkit/TUIkit.docc/TUIkit.md b/Sources/TUIkit/TUIkit.docc/TUIkit.md index c9c10204..60aa0173 100644 --- a/Sources/TUIkit/TUIkit.docc/TUIkit.md +++ b/Sources/TUIkit/TUIkit.docc/TUIkit.md @@ -74,6 +74,8 @@ struct MyApp: App { - ``View`` - ``Text`` +- ``Image`` +- ``ImageSource`` - ``EmptyView`` - ``AnyView`` - ``Spinner`` @@ -153,6 +155,7 @@ struct MyApp: App { - ``Appearance`` - ``BorderStyle`` +- ``ContentMode`` - ``EdgeInsets`` - ``Edge`` @@ -169,6 +172,7 @@ struct MyApp: App { - ``InsetGroupedListStyle`` - ``SpinnerStyle`` - ``TextCursorStyle`` +- ``TextContentType`` - ``NavigationSplitViewStyle`` ### View Composition diff --git a/Sources/TUIkit/Views/Image.swift b/Sources/TUIkit/Views/Image.swift new file mode 100644 index 00000000..73755280 --- /dev/null +++ b/Sources/TUIkit/Views/Image.swift @@ -0,0 +1,265 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// Image.swift +// +// Created by LAYERED.work +// License: MIT + +import Foundation + +// MARK: - Image Source + +/// Describes where to load an image from. +public enum ImageSource: Sendable, Equatable { + /// Load from a local file path. + case file(String) + + /// Load from a URL (cached per session). + case url(String) +} + +// MARK: - Image Loading Phase + +/// Represents the current state of an async image loading operation. +enum ImageLoadingPhase: Sendable { + /// Loading has not started yet or is in progress. + case loading + + /// The raw image was successfully loaded and is ready for conversion. + case success(RGBAImage) + + /// Loading failed with an error. + case failure(String) +} + +// MARK: - Image + +/// Displays an image as colored ASCII art in the terminal. +/// +/// `Image` loads a raster image from a file path or URL, converts it to +/// colored ASCII characters, and displays it at the specified size. +/// Loading happens asynchronously; a placeholder is shown while loading. +/// +/// ## Usage +/// +/// ```swift +/// // From a local file +/// Image(.file("/path/to/logo.png")) +/// .frame(width: 60, height: 30) +/// +/// // From a URL (cached per session) +/// Image(.url("https://example.com/photo.png")) +/// .frame(width: 40, height: 20) +/// +/// // With rendering options +/// Image(.file("photo.png")) +/// .imageCharacterSet(.braille) +/// .imageColorMode(.trueColor) +/// .imageDithering(.floydSteinberg) +/// .frame(width: 80, height: 40) +/// ``` +/// +/// ## Placeholder +/// +/// While loading, a centered placeholder is displayed. By default this is +/// a ``Spinner``. Use ``View/imagePlaceholder(_:)`` to customize. +public struct Image: View { + /// The image source (file path or URL). + let source: ImageSource + + /// Creates an image from the given source. + /// + /// - Parameter source: The image source (file or URL). + public init(_ source: ImageSource) { + self.source = source + } + + public var body: some View { + _ImageCore(source: source) + } +} + +// MARK: - Equatable + +extension Image: Equatable { + nonisolated public static func == (lhs: Image, rhs: Image) -> Bool { + lhs.source == rhs.source + } +} + +// MARK: - Environment Keys + +/// Environment key for the ASCII character set used by Image. +private struct ImageCharacterSetKey: EnvironmentKey { + static let defaultValue: ASCIICharacterSet = .blocks +} + +/// Environment key for the color mode used by Image. +private struct ImageColorModeKey: EnvironmentKey { + static let defaultValue: ASCIIColorMode = .trueColor +} + +/// Environment key for the dithering mode used by Image. +private struct ImageDitheringKey: EnvironmentKey { + static let defaultValue: DitheringMode = .none +} + +/// Environment key for the placeholder text shown while loading. +private struct ImagePlaceholderTextKey: EnvironmentKey { + static let defaultValue: String? = nil +} + +/// Environment key controlling whether a spinner is shown while loading. +private struct ImagePlaceholderSpinnerKey: EnvironmentKey { + static let defaultValue: Bool = true +} + +/// Environment key for the image content mode. +private struct ImageContentModeKey: EnvironmentKey { + static let defaultValue: ContentMode = .fit +} + +/// Environment key for an explicit aspect ratio override. +private struct ImageAspectRatioKey: EnvironmentKey { + static let defaultValue: Double? = nil +} + +// MARK: - EnvironmentValues + +extension EnvironmentValues { + /// The character set for ASCII art rendering. + var imageCharacterSet: ASCIICharacterSet { + get { self[ImageCharacterSetKey.self] } + set { self[ImageCharacterSetKey.self] = newValue } + } + + /// The color mode for ASCII art rendering. + var imageColorMode: ASCIIColorMode { + get { self[ImageColorModeKey.self] } + set { self[ImageColorModeKey.self] = newValue } + } + + /// The dithering mode for ASCII art rendering. + var imageDithering: DitheringMode { + get { self[ImageDitheringKey.self] } + set { self[ImageDitheringKey.self] = newValue } + } + + /// Custom placeholder text shown while loading (nil = no text). + var imagePlaceholderText: String? { + get { self[ImagePlaceholderTextKey.self] } + set { self[ImagePlaceholderTextKey.self] = newValue } + } + + /// Whether to show a spinner in the placeholder. + var imagePlaceholderSpinner: Bool { + get { self[ImagePlaceholderSpinnerKey.self] } + set { self[ImagePlaceholderSpinnerKey.self] = newValue } + } + + /// The content mode for image scaling. + var imageContentMode: ContentMode { + get { self[ImageContentModeKey.self] } + set { self[ImageContentModeKey.self] = newValue } + } + + /// An explicit aspect ratio override for images (width/height). + /// + /// When `nil`, the source image's natural aspect ratio is used. + var imageAspectRatio: Double? { + get { self[ImageAspectRatioKey.self] } + set { self[ImageAspectRatioKey.self] = newValue } + } +} + +// MARK: - View Modifiers + +extension View { + + /// Sets the character set for ASCII art image rendering. + /// + /// - Parameter characterSet: The character set to use. + /// - Returns: A modified view. + public func imageCharacterSet(_ characterSet: ASCIICharacterSet) -> some View { + environment(\.imageCharacterSet, characterSet) + } + + /// Sets the color mode for ASCII art image rendering. + /// + /// - Parameter colorMode: The color mode to use. + /// - Returns: A modified view. + public func imageColorMode(_ colorMode: ASCIIColorMode) -> some View { + environment(\.imageColorMode, colorMode) + } + + /// Sets the dithering mode for ASCII art image rendering. + /// + /// - Parameter dithering: The dithering algorithm. + /// - Returns: A modified view. + public func imageDithering(_ dithering: DitheringMode) -> some View { + environment(\.imageDithering, dithering) + } + + /// Sets the placeholder text shown while an image is loading. + /// + /// - Parameter text: The placeholder text, or nil for no text. + /// - Returns: A modified view. + public func imagePlaceholder(_ text: String?) -> some View { + environment(\.imagePlaceholderText, text) + } + + /// Controls whether a spinner is shown while an image is loading. + /// + /// - Parameter showSpinner: Whether to show a spinner. + /// - Returns: A modified view. + public func imagePlaceholderSpinner(_ showSpinner: Bool) -> some View { + environment(\.imagePlaceholderSpinner, showSpinner) + } + + /// Sets the aspect ratio and content mode for image rendering. + /// + /// Use this modifier to control how images are scaled within their + /// available space. + /// + /// ```swift + /// // Use natural aspect ratio, fit within bounds + /// Image(.file("photo.png")) + /// .aspectRatio(contentMode: .fit) + /// + /// // Force 16:9 ratio, fill bounds + /// Image(.url("https://example.com/banner.png")) + /// .aspectRatio(16.0/9.0, contentMode: .fill) + /// ``` + /// + /// - Parameters: + /// - aspectRatio: The ratio of width to height to use for the + /// resulting view. Use `nil` to maintain the source image's + /// natural aspect ratio. + /// - contentMode: A flag that indicates whether this view fits or + /// fills the parent context. + /// - Returns: A view that constrains this view's dimensions to the + /// given aspect ratio and content mode. + public func aspectRatio(_ aspectRatio: Double? = nil, contentMode: ContentMode) -> some View { + environment(\.imageContentMode, contentMode) + .environment(\.imageAspectRatio, aspectRatio) + } + + /// Scales this view to fit within the parent while maintaining the + /// aspect ratio. + /// + /// Equivalent to `.aspectRatio(contentMode: .fit)`. + /// + /// - Returns: A view that scales to fit. + public func scaledToFit() -> some View { + aspectRatio(contentMode: .fit) + } + + /// Scales this view to fill the parent while maintaining the + /// aspect ratio. + /// + /// Equivalent to `.aspectRatio(contentMode: .fill)`. + /// + /// - Returns: A view that scales to fill. + public func scaledToFill() -> some View { + aspectRatio(contentMode: .fill) + } +} diff --git a/Sources/TUIkit/Views/SecureField.swift b/Sources/TUIkit/Views/SecureField.swift index 9190065c..a29e13ce 100644 --- a/Sources/TUIkit/Views/SecureField.swift +++ b/Sources/TUIkit/Views/SecureField.swift @@ -276,6 +276,7 @@ private struct _SecureFieldCore: View, Renderable, Layoutable { handler.text = text handler.canBeFocused = !isDisabled handler.onSubmit = onSubmitAction + handler.textContentType = context.environment.textContentType handler.clampCursorPosition() FocusRegistration.register(context: context, handler: handler) diff --git a/Sources/TUIkit/Views/TextField.swift b/Sources/TUIkit/Views/TextField.swift index f688527b..17675908 100644 --- a/Sources/TUIkit/Views/TextField.swift +++ b/Sources/TUIkit/Views/TextField.swift @@ -285,6 +285,7 @@ private struct _TextFieldCore: View, Renderable, Layoutable { handler.text = text handler.canBeFocused = !isDisabled handler.onSubmit = onSubmitAction + handler.textContentType = context.environment.textContentType handler.clampCursorPosition() FocusRegistration.register(context: context, handler: handler) diff --git a/Sources/TUIkit/Views/_ImageCore.swift b/Sources/TUIkit/Views/_ImageCore.swift new file mode 100644 index 00000000..0764e2bb --- /dev/null +++ b/Sources/TUIkit/Views/_ImageCore.swift @@ -0,0 +1,258 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// _ImageCore.swift +// +// Created by LAYERED.work +// License: MIT + +import Foundation + +// MARK: - State Indices + +/// Named property indices for `_ImageCore` state storage. +private enum StateIndex { + /// Stores the loading phase (`ImageLoadingPhase`). + static let phase = 0 + + /// Stores the last loaded source for change detection (`ImageSource`). + static let lastSource = 1 +} + +// MARK: - Image Core + +/// Private rendering implementation for ``Image``. +/// +/// Handles async image loading, caching, and placeholder display. +/// The raw `RGBAImage` is cached in state; ASCII conversion happens +/// on every render pass so that environment changes (character set, +/// color mode, dithering) take effect immediately. +struct _ImageCore: View, Renderable, Layoutable { + /// The image source. + let source: ImageSource + + var body: Never { + fatalError("_ImageCore renders via Renderable") + } + + // MARK: - Layoutable + + func sizeThatFits(proposal: ProposedSize, context: RenderContext) -> ViewSize { + let w = proposal.width ?? context.availableWidth + let h = proposal.height ?? context.availableHeight + return .fixed(w, h) + } + + // MARK: - Renderable + + func renderToBuffer(context: RenderContext) -> FrameBuffer { + let stateStorage = context.tuiContext.stateStorage + let lifecycle = context.tuiContext.lifecycle + let identity = context.identity + + let width = context.availableWidth + let height = context.availableHeight + + guard width > 0, height > 0 else { + return FrameBuffer() + } + + // Read environment values + let characterSet = context.environment.imageCharacterSet + let colorMode = context.environment.imageColorMode + let dithering = context.environment.imageDithering + let contentMode = context.environment.imageContentMode + let aspectRatioOverride = context.environment.imageAspectRatio + let placeholderText = context.environment.imagePlaceholderText + let showSpinner = context.environment.imagePlaceholderSpinner + + // Retrieve or create persistent phase state + let phaseKey = StateStorage.StateKey(identity: identity, propertyIndex: StateIndex.phase) + let phaseBox: StateBox = stateStorage.storage(for: phaseKey, default: .loading) + stateStorage.markActive(identity) + + // Track the last loaded source to detect changes + let sourceKey = StateStorage.StateKey(identity: identity, propertyIndex: StateIndex.lastSource) + let lastSourceBox: StateBox = stateStorage.storage(for: sourceKey, default: nil) + + // Build a unique token for this image source + let token = "image-\(identity.path)" + + // Detect source change and force reload + if let lastSource = lastSourceBox.value, lastSource != source { + lifecycle.cancelTask(token: token) + lifecycle.resetAppearance(token: token) + phaseBox.value = .loading + } + lastSourceBox.value = source + + // Start loading on first appearance + if !lifecycle.hasAppeared(token: token) { + _ = lifecycle.recordAppear(token: token) {} + + let src = source + lifecycle.startTask(token: token, priority: .userInitiated) { + let loader = PlatformImageLoader() + + do { + let rawImage: RGBAImage + switch src { + case .file(let path): + rawImage = try loader.loadImage(from: path) + case .url(let urlString): + rawImage = try loader.loadImage(from: urlString, cache: .shared) + } + + // Store the raw image; conversion happens per render pass. + // StateBox.didSet triggers setNeedsRender() automatically. + // Do NOT use MainActor.run here: the render loop blocks the + // main actor with usleep, so MainActor.run would deadlock. + phaseBox.value = .success(rawImage) + } catch let loadError as ImageLoadError { + phaseBox.value = .failure(loadError.description) + } catch { + phaseBox.value = .failure(error.localizedDescription) + } + } + } else { + _ = lifecycle.recordAppear(token: token) {} + } + + // Cancel loading task on disappear + lifecycle.registerDisappear(token: token) { [lifecycle] in + lifecycle.cancelTask(token: token) + } + + // Render based on current phase + switch phaseBox.value { + case .loading: + return renderPlaceholder( + width: width, + height: height, + text: placeholderText, + showSpinner: showSpinner, + context: context + ) + + case .success(let rawImage): + return renderImage( + rawImage, + width: width, + height: height, + characterSet: characterSet, + colorMode: colorMode, + dithering: dithering, + contentMode: contentMode, + aspectRatioOverride: aspectRatioOverride + ) + + case .failure(let message): + return renderError(message, width: width, height: height, context: context) + } + } +} + +// MARK: - Image Rendering + +extension _ImageCore { + + /// Converts the raw image to ASCII art for the current frame dimensions and settings. + private func renderImage( + _ rawImage: RGBAImage, + width: Int, + height: Int, + characterSet: ASCIICharacterSet, + colorMode: ASCIIColorMode, + dithering: DitheringMode, + contentMode: ContentMode, + aspectRatioOverride: Double? + ) -> FrameBuffer { + let targetSize = ASCIIConverter.targetSize( + imageWidth: rawImage.width, + imageHeight: rawImage.height, + maxWidth: width, + maxHeight: height, + contentMode: contentMode, + overrideAspectRatio: aspectRatioOverride + ) + + guard targetSize.width > 0, targetSize.height > 0 else { + return FrameBuffer() + } + + let converter = ASCIIConverter( + characterSet: characterSet, + colorMode: colorMode, + dithering: dithering + ) + let lines = converter.convert(rawImage, width: targetSize.width, height: targetSize.height) + return FrameBuffer(lines: lines) + } +} + +// MARK: - Placeholder Rendering + +extension _ImageCore { + + /// Renders a centered placeholder with optional spinner and text. + private func renderPlaceholder( + width: Int, + height: Int, + text: String?, + showSpinner: Bool, + context: RenderContext + ) -> FrameBuffer { + let palette = context.environment.palette + + // Build placeholder content lines + var contentLines: [String] = [] + + if showSpinner { + let spinnerText = "⠋" + let colored = ANSIRenderer.colorize(spinnerText, foreground: palette.accent) + contentLines.append(colored) + } + + if let text { + let colored = ANSIRenderer.colorize(text, foreground: palette.foregroundSecondary) + contentLines.append(colored) + } + + if contentLines.isEmpty { + contentLines.append(ANSIRenderer.colorize("Loading...", foreground: palette.foregroundSecondary)) + } + + return centerContent(contentLines, width: width, height: height) + } + + /// Renders an error message centered in the frame. + private func renderError( + _ message: String, + width: Int, + height: Int, + context: RenderContext + ) -> FrameBuffer { + let palette = context.environment.palette + let errorText = ANSIRenderer.colorize("Error: \(message)", foreground: palette.error) + return centerContent([errorText], width: width, height: height) + } + + /// Centers content lines vertically and horizontally within the given dimensions. + private func centerContent(_ contentLines: [String], width: Int, height: Int) -> FrameBuffer { + let emptyLine = String(repeating: " ", count: width) + var lines = [String](repeating: emptyLine, count: height) + + let startY = max(0, (height - contentLines.count) / 2) + + for (i, content) in contentLines.enumerated() { + let y = startY + i + guard y < height else { break } + + // Calculate visible width of content (excluding ANSI codes) + let visibleWidth = content.filter { !$0.isASCII || ($0.asciiValue ?? 0) >= 32 }.count + let padding = max(0, (width - visibleWidth) / 2) + let padded = String(repeating: " ", count: padding) + content + lines[y] = padded + } + + return FrameBuffer(lines: lines, width: width) + } +} diff --git a/Sources/TUIkitExample/ContentView.swift b/Sources/TUIkitExample/ContentView.swift index 1225f606..0a367834 100644 --- a/Sources/TUIkitExample/ContentView.swift +++ b/Sources/TUIkitExample/ContentView.swift @@ -27,6 +27,8 @@ enum DemoPage: Int, CaseIterable { case sliders case steppers case splitView + case imageFile + case imageURL } // MARK: - Content View (Page Router) @@ -56,44 +58,12 @@ struct ContentView: View { return true // Consumed } return false // Let default handler exit the app - case .character("8"): - // Quick jump to Text Fields - currentPage = .textFields - return true - case .character("\\"): - // Quick jump to Secure Fields - currentPage = .secureFields - return true - case .character("9"): - // Quick jump to Radio Buttons - currentPage = .radioButtons - return true - case .character("0"): - // Quick jump to Spinners - currentPage = .spinners - return true - case .character("-"): - // Quick jump to Lists - currentPage = .lists - return true - case .character("="): - // Quick jump to Tables - currentPage = .tables - return true - case .character("["): - // Quick jump to Sliders - currentPage = .sliders - return true - case .character("]"): - // Quick jump to Steppers - currentPage = .steppers - return true - case .character(";"): - // Quick jump to Split View - currentPage = .splitView - return true default: - return false // Let other handlers process + // Quick-jump shortcuts only work from the menu page. + // On sub-pages they would conflict with text input + // (e.g. TextField, SecureField). + guard currentPage == .menu else { return false } + return handleMenuShortcut(event.key) } } } @@ -155,6 +125,10 @@ struct ContentView: View { case .splitView: SplitViewPage() .statusBarItems(subPageItems(pageSetter: pageSetter)) + case .imageFile: + ImageFilePage() + case .imageURL: + ImageURLPage() } } @@ -167,4 +141,24 @@ struct ContentView: View { StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"), ] } + + /// Handles quick-jump shortcuts from the menu page. + /// + /// - Returns: `true` if the key was consumed, `false` otherwise. + private func handleMenuShortcut(_ key: Key) -> Bool { + let mapping: [Character: DemoPage] = [ + "1": .textStyles, "2": .colors, "3": .containers, + "4": .overlays, "5": .layout, "6": .buttons, + "7": .toggles, "8": .textFields, "\\": .secureFields, + "9": .radioButtons, "0": .spinners, "-": .lists, + "=": .tables, "[": .sliders, "]": .steppers, + ";": .splitView, "'": .imageFile, ",": .imageURL, + ] + + if case .character(let ch) = key, let page = mapping[ch] { + currentPage = page + return true + } + return false + } } diff --git a/Sources/TUIkitExample/Pages/ImageFilePage.swift b/Sources/TUIkitExample/Pages/ImageFilePage.swift new file mode 100644 index 00000000..cff73daa --- /dev/null +++ b/Sources/TUIkitExample/Pages/ImageFilePage.swift @@ -0,0 +1,93 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// ImageFilePage.swift +// +// Created by LAYERED.work +// License: MIT + +import Foundation +import TUIkit + +/// Image demo page for loading an image from the local filesystem. +/// +/// Displays a bundled demo image and provides status bar items to +/// cycle through character set, color mode, and dithering settings. +struct ImageFilePage: View { + @State var charSetIndex: Int = 0 + @State var colorModeIndex: Int = 0 + @State var ditheringOn: Bool = false + + var body: some View { + let charSet = Self.charSets[charSetIndex] + let colorMode = Self.colorModes[colorModeIndex] + let dithering: DitheringMode = ditheringOn ? .floydSteinberg : .none + + VStack(alignment: .leading) { + HStack { + Spacer() + if let path = Bundle.module.path(forResource: "demo-image", ofType: "jpg", inDirectory: "Resources") { + Image(.file(path)) + .imagePlaceholder("Loading image...") + .imagePlaceholderSpinner(true) + } else { + Text("Resource not found: demo-image.jpg") + .foregroundStyle(.error) + } + Spacer() + } + .padding(.bottom, 1) + Spacer() + } + .imageCharacterSet(charSet) + .imageColorMode(colorMode) + .imageDithering(dithering) + .statusBarItems(statusBarItems) + .appHeader { + HStack { + Text("Image (File)").bold().foregroundStyle(.palette.accent) + Spacer() + Text("TUIkit v\(tuiKitVersion)").foregroundStyle(.palette.foregroundTertiary) + } + } + } + + private var statusBarItems: [any StatusBarItemProtocol] { + [ + StatusBarItem(shortcut: Shortcut.escape, label: "back"), + StatusBarItem(shortcut: "c", label: Self.charSetLabel(charSetIndex)) { + charSetIndex = (charSetIndex + 1) % Self.charSets.count + }, + StatusBarItem(shortcut: "m", label: Self.colorModeLabel(colorModeIndex)) { + colorModeIndex = (colorModeIndex + 1) % Self.colorModes.count + }, + StatusBarItem(shortcut: "d", label: ditheringOn ? "dither:on" : "dither:off") { + ditheringOn.toggle() + }, + StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"), + ] + } +} + +// MARK: - Modifier Options + +extension ImageFilePage { + + static let charSets: [ASCIICharacterSet] = [.blocks, .ascii, .braille] + static let colorModes: [ASCIIColorMode] = [.trueColor, .ansi256, .grayscale, .mono] + + static func charSetLabel(_ index: Int) -> String { + switch charSets[index] { + case .ascii: return "chars:ascii" + case .blocks: return "chars:blocks" + case .braille: return "chars:braille" + } + } + + static func colorModeLabel(_ index: Int) -> String { + switch colorModes[index] { + case .trueColor: return "color:true" + case .ansi256: return "color:256" + case .grayscale: return "color:gray" + case .mono: return "color:mono" + } + } +} diff --git a/Sources/TUIkitExample/Pages/ImageURLPage.swift b/Sources/TUIkitExample/Pages/ImageURLPage.swift new file mode 100644 index 00000000..f8857e43 --- /dev/null +++ b/Sources/TUIkitExample/Pages/ImageURLPage.swift @@ -0,0 +1,114 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// ImageURLPage.swift +// +// Created by LAYERED.work +// License: MIT + +import Foundation +import TUIkit + +/// Image demo page for loading an image from a URL. +/// +/// Provides a text field for entering an image URL. After pressing +/// Enter the image is downloaded and rendered. Status bar items allow +/// cycling through character set, color mode, and dithering settings. +struct ImageURLPage: View { + @State var imageURL: String = "" + @State var activeURL: String = "" + + @State var charSetIndex: Int = 0 + @State var colorModeIndex: Int = 0 + @State var ditheringOn: Bool = false + + var body: some View { + let charSet = Self.charSets[charSetIndex] + let colorMode = Self.colorModes[colorModeIndex] + let dithering: DitheringMode = ditheringOn ? .floydSteinberg : .none + + VStack(alignment: .leading) { + HStack(spacing: 1) { + Text("URL:") + .foregroundStyle(.palette.foregroundSecondary) + TextField("Enter image URL...", text: $imageURL) + .onSubmit { + activeURL = imageURL + } + .textContentType(.url) + } + .padding(.bottom, 1) + + if !activeURL.isEmpty { + HStack { + Spacer() + Image(.url(activeURL)) + .imagePlaceholder("Downloading...") + .imagePlaceholderSpinner(true) + .border(color: .palette.border) + Spacer() + } + } else { + HStack { + Spacer() + Text("Press Enter to load the image") + .foregroundStyle(.palette.foregroundTertiary) + .italic() + Spacer() + } + Spacer() + } + Spacer() + } + .imageCharacterSet(charSet) + .imageColorMode(colorMode) + .imageDithering(dithering) + .statusBarItems(statusBarItems) + .appHeader { + HStack { + Text("Image (URL)").bold().foregroundStyle(.palette.accent) + Spacer() + Text("TUIkit v\(tuiKitVersion)").foregroundStyle(.palette.foregroundTertiary) + } + } + } + + private var statusBarItems: [any StatusBarItemProtocol] { + [ + StatusBarItem(shortcut: Shortcut.escape, label: "back"), + StatusBarItem(shortcut: "c", label: Self.charSetLabel(charSetIndex)) { + charSetIndex = (charSetIndex + 1) % Self.charSets.count + }, + StatusBarItem(shortcut: "m", label: Self.colorModeLabel(colorModeIndex)) { + colorModeIndex = (colorModeIndex + 1) % Self.colorModes.count + }, + StatusBarItem(shortcut: "d", label: ditheringOn ? "dither:on" : "dither:off") { + ditheringOn.toggle() + }, + StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"), + ] + } +} + +// MARK: - Modifier Options + +extension ImageURLPage { + + static let charSets: [ASCIICharacterSet] = [.blocks, .ascii, .braille] + static let colorModes: [ASCIIColorMode] = [.trueColor, .ansi256, .grayscale, .mono] + + static func charSetLabel(_ index: Int) -> String { + switch charSets[index] { + case .ascii: return "chars:ascii" + case .blocks: return "chars:blocks" + case .braille: return "chars:braille" + } + } + + static func colorModeLabel(_ index: Int) -> String { + switch colorModes[index] { + case .trueColor: return "color:true" + case .ansi256: return "color:256" + case .grayscale: return "color:gray" + case .mono: return "color:mono" + } + } +} diff --git a/Sources/TUIkitExample/Pages/MainMenuPage.swift b/Sources/TUIkitExample/Pages/MainMenuPage.swift index cf922adc..8e066de7 100644 --- a/Sources/TUIkitExample/Pages/MainMenuPage.swift +++ b/Sources/TUIkitExample/Pages/MainMenuPage.swift @@ -69,6 +69,8 @@ struct MainMenuPage: View { MenuItem(label: "Sliders", shortcut: "["), MenuItem(label: "Steppers", shortcut: "]"), MenuItem(label: "Split View", shortcut: ";"), + MenuItem(label: "Image (File)", shortcut: "'"), + MenuItem(label: "Image (URL)", shortcut: ","), ], selection: $menuSelection, onSelect: { index in diff --git a/Sources/TUIkitExample/Resources/demo-image.jpg b/Sources/TUIkitExample/Resources/demo-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7b337e0de0ee439693e5cc56b58472bc0832aaac GIT binary patch literal 236628 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%0Dx_Q;9~yBQ>Kdn^D{iCm4%4uGWLqHZ%nm=vU8xQ zo*h|YOd-ojOLXgmsjdaEb77yNs|@)Rc^H$f_86n@;+`J?aUg)QU7!*cvdrhJy`Z_5>@Gy zZvcHX#5yqpW1~Hl?cl*|q{oncJVjyh&y3C0+6TEcVNWzxM3$ZHQo(rq^d+QTG@dwS=w3*{>Dt|y<8}>e1ucSd zP~%frU^^`85MOZM<-t3?bav|1R!T!0mJa_Q8H%H3A=9dJWESMK_g{<+Qd%Irw|}6C zS~mtg(11?k36A{RsrDmz<&40RAD~$>Z(`4T2U_#=KMG-a-EU{mL%_6g?e*9VVm9_t zm7iZA)M6_AGdtl}=8~W8j)G_n=NGV9yKs8f-AA91Q9Lw2ehRWhM@jji!y@m5Wpu^* z3jarlkmYT>113%kzeP&795)Jq8fZDdv)K-bCh2$phm$}RyO%GE68v-To}K7>-a z@-q#F^>UdATaNu{TXdPtHdjJwEiPDFj&&d7i~Ns521U2Si3yhJgRsI8W(^r(o;+)T zo4?>_i@NgoUn-PAmc; zvAyM#2>Cd6U-VS!TbIzE!TKRLz%2XduVuNE-e%^nb@pQA>+X{&4IXU1g25!pY;%X& zLYJKB+2|fu=xL@{C1XH2>{})q#7>8h)j22WJER9=)v8WDtNP`f;5K21f`p&eCzTC2KVCL-wxO&%VC_w z;tDUGktfJv#1t!G@<3&hbPRtO<)huusaCJWHMGl#!@0#}66LY~|O{8gCq^5T%K zN1COgDXgNkIGARiX5pBXWMx7VtXUJ$b&&Kdh3`Poe{jjeb9oB&6`LXxQ!Z8Y*z;cZ z;BBeL6ck=caeaVS4k0?pKdSV^TNET(8?~Clo3-jpwOxN3_layFq>lT|u|;Mr0ia2y zIBwG0F@ItB0DoS&r1qMB|J%o>RKt7J7~dXAv<>^C8t|im=Q+PmVA(}0W?)=vCIf)i z`kIdAZA9=m|D*FKA2`1F9^RED>Y&@n#vCI6qT8eeM&0obcgj#tm{mTjxiq&S1S&pE zIU)E-T2uZXt@kGl26(D}B!0TJ9~cKq0nebyG0T~l8_r}!#MHUzJ0qms7BBC|BWky5 zby10(qg#P6&B($%Hfi6jQ(i5b@MK=bixx9-*m2+mmp>hTXt(@A+78V$wLUNbC^n@} z4K7m6YWd^3mlLlj5n?~nlC>euE-@((Uf{A~blZZo>!CDJ`(q>zff&=QL(at6=DAun+yY5j^25_<`Ja zIXQ}VED;3d%7bWVHF?sUKV+Ar2w1FMF>RQ#HP+Gk{GD0b2Ntmd%ciBac$1kf=affB zxLU2|oi+b^*we7S=3?|nhF|PSwQ{@P^3epT<$*<<5;$= z)~T3by;x_IOE8N+{P(dpCbS6?^?r=d^v_(vve4SAv>pnez0`1DewXYGID6vU95 z>5EkF&}zSb^AF~PW7J*tW3{nnVgUaTey+C1ywbPCN<_YF7)!Oo!6#SKujjmzE##)& zI7BVgDTbH5{-c+Rl!m9ISaTrEl$-^)%b^f8G=-hl$+>c5Qx-d6ZJ-fQ^)I2QK*C!v zgPqA5s6mOeLnt%z13@C%IcMh-L%WP%v>ltVb=9!5XPNP@fuoQ1K^=|F0bIp)4c|KB z6(oMD_1$|fdE+V{U^Vj4Ta#$>A! z%yPY42FLwDz=U=E%rz^Z;;9<9+Zt2{(23B{*`0h1mq7*}g^%y?n(c%P`IC43r%|2o z1nC{Mc?kLwx9ojkGgES&Xn`n|*rH1c-E5=Ke(vX+t4{NjbJHuB6iq5JBWU>XKFjFQ z@?fE>z~yWg!;FBMl4^lSY#nN|01J)uhv7gJn~O4peE=i9-dXg=qiFyok39@H$zB!MF?r$=+Li4 zs`swmKkt&fMSNqdf-8h}O0eyZct8Wpy-2>j zGdD%ds#Y#AmV}c@bMq^|!FTrx{Dif?=Xa}#LoLxDU@(ge<&Ne}I+=Ef0V$hjHh4gq zQ}t^Xzxwy4g{))*B zTw(`33h*OLwfhcc;^W#?UVPyQJK!DM7S@$^_fuxZy&ZWVv`p=YtQb zm37_U`s?((-ZH~Djl4T#;}NlJ6@qT{fqLr{WDu*(;3kJE}sC8gR;K`;7|ebQ;OoG`3dF`%C)uk5-td<^w`;lrQ3LSWXVEz4e$Ki4Hz> zIL;ugJIdI^|=#1f@TU( zZewRodPE*m8&$HQ46hKlZJNIksJbt~F`wP|E$}VjafTFA>Jau%qR}CO*OD9m@9-g@ z9(-iA8L$O>gB_xb9RcY41eQ?8I7StEXgp|l;C;)OsQ%rgK~;7u8?A{`Bv7#WT;t+7 zqL<7<%yvDH&Etm5obL8jUH+O21>;;hw}Ak3R%{%Qr0Y*u+8w}M$G&>(l$+3L#F2FO zCwtPTq8_2YHcHSH8WMBciqldMnh)M}Vo#8_wQjz$KPoJK#dL<|7CrKY&?`m1y;A?v z$K-)QzIPCYm)Ym!SCNKI=(iboNFEbsE?5*FeQqqUWqG-4qD7EL`sp%AD-+pPqj~g>{ z{>pRL!$JXHWPw zx5=59%kDg(l6n8_m32KXJ(sID-Czlsx2m>MIp#Phc(&U(7KSO80%1v<`$5T^IkWSX zbDCFYVmD~ku5P%_GAc*LN(^2@_-P2{#pt#Sd6thvbm+I}#lRhhjuVNQ7hFI*D=Xas zysOpg*J?01{=M-+=c^~eHzH`9Wh04W9ZPuirO)Thjj}N-nas9ZLT6S+cd%ND*HYkJ zhp4tCq9BySCfEVFQq|`YlvzD4$2s+-WcB9UPET@uMO@}WAGi9DC|sV$*FCVCTnS|7 zsI`FZ`18=jBJMcIHk?FxT#a!5fX(5KKRQ(RkG}bIZWMECWstq|{X>WyJ~Maq2O7lo zddD5$M#wjE@J!fiNqeVZA(M^8i=8(Yx4$sod3I6Ap)-6A-OxPU)xfGF#5RqW|D$*j z_NK{XhAnBG?~rQ9b7Lo!oTlXCaHqslpRZ@A05NeEL1;7>CEfcY8^V)d277(uYv>^5 z>7FTvfRjPwYIJP6mTp+#W1C^DH9-<-7xu1#E$OMLM-MJD4`FI=+@G4+3AzJwJOj_` zm2s@;zYG{-M30%a+Eg(+C(7Y78zg2i61y@<4HGgYpBQj^wWTwH9D7~P69bWni9V|i z@0izC!-paeB7W=##d8A@*7sOzodunlsA?BcWtio3abq~<#FiMGV@egHwQu<0!J&!6 zwH1sw_>qHe-d=y`&}pZD*k`@KB=a0RY7D4RGh58dCsAgcTimujFmnCW0=<^bjhk)$ zZjr}*>-P|E3MS$tAnqE`=Z`k{t8~eWTyvEq>xG~+T+5ts<})FHbDy1n-kQn zrs}Yl#GI8!f^Tgg|xVloBGrJkF3G^eY;g4sSpmuqpf2U7n82aFP+Nr(SU~8 zMO{8E`l!PMX!-?bM~2~=&jW{ueRhWFg|Fc6TZWFqnM<0&l1TQCT|-NeC^wJzr@c5^ zKE<&>@;*(T$HOl?FW6??wKwjo#}anWrh~_7u`n=sOyM>dOw{mU%|oS5y7}1ex(lY% zA&vj}xO~;#c$$^#=OgJf#7+k7=*ev98jm@9SQMN^d`eTfF{C>dS1O_~x?x(pmCb07 zi{$%{g1M@oY<%H*SN{Th(`%(p=y<(xY+r^+_Q3^sR3}}e=8&fQCt(~{9pHYUMSN=u z7DrbZwhARK-|Gz$XM}JP2VY36X&aLkn;r?o{V9NWvTiR#=r@Hoh(@x1WdB;M%s9%( zq;(yf^e(b}d~$r>$+;lePu*bVf#nVhi$3Lp5YuEw%D^KQcyX#w)|JRl(dqg-JFkkD zdg@|w9EIEHLEoKWB#%gnAwY~uxpn%d^WV+7hg`1gf=^6&C*J*kaC1SYI&eRzsBv63&5=ZjRW;Z7dTLoN9xO^ zu)Ul{@^n?gW)IaPOp06rU&^NyRbG<=%1>APo4$Ix1^10Sy^}vv=5+EO#m*^Y^v}pk z3S}_9WI*i2geOwQW6sW5vq@7rNT@%AFUDJ{W~BAn)o;`E$E=~5aH!8RCY)D_=&pP@ z6j=S_ct*wvyz{ET&uebO$XwN;J`j*%?{x4SV=RspYVAul?EuaUm9b@Jz3G05wb5v`|U2Z}p_BZz>vaoKxuiD)7H{dQPtg zBpdv{O57)K0Pp_vvp~k#?&jb7dH^R;OW^%!|NSKYbFFn+UeDIjRxI*0xd6Ew0;BWZ zVFSxGh2N_=t9{a`%6$`@4n5q%?s(NT8%}Wpb)DD_q}vky)e@yKTz8qXv2(}Mqr8@) zP@n&lYG+VnzDL*`suVqm*o&L4+voqOlMP$gqwv0Hayy$$RZn}~lYS0mbsl|oBZj~G zZLsZLm+zvacG-3H#Vq&F@Pn>V^dMrlWYxcQWO|D)`EDH*fJ`6 zaPspe*vhrJ>MtW#D6cZdq9>$nFV;nV~tAXKx z$Yo~09IS2R5{; zYy_7%k|2Qw>k*<;yq`V(Xo`bZuW=?a&Wg12?@gB&TCLyT+9W!tKm7qB4~|i<9{IPm zLKQ`9W}#9SR}sLn?G#Bh3Q&uW{P!mua4 zFTRU>Z0F*_+WqKM`%UzrHUM^@moLMR&Y&nznRojLZYdO7ttoII{Kqx{eXmKM#*QJl zI>pcHAdzHv1~XHLW{GsOQ93O;2R z+=}K5A98oa-g22&iOnMX<<7KU62mH?gu|-c=8oy-BHYgY=sjtQ`;EQt zG-?BChz?;C%t2Cr&uPiw2qq%9?bYbsZi28=!qwT}(1o7ktx2@nWfIc*3~`gFFN%7U zs`=*Ul$-^=RBQjXGaprr$gz2wnx-tB{GNe<-3Lp!`q%5SG~@BH>@(StiZTF#{4R%u1-Yx2_Z(tUug^0o zoRiH&whxFxG#!b=mJ2)hQD^dKa#h(+X+J%yy6bzE%%7PYzcTncSL`D#swftJ5{Oes z1`Cu9=vg{+wE)rC{;re`vk=E5;i>dy*9V(vq3 z<_CeM9G!aGoeVEc^!P@uIbF@1A|Qkv#L+ZL|SDm7nzL1R{r1`cwq6(%!># zlie}PC=lBvdc!|7;oPUGvH?Kg6#YWLu%9+4i7#8<7WZm|>7{_2GF z0)ov7EF3Mg-9KvPAQy}4pEX%m^56qKrqP;cGv_dIh5h?M<*ZnSJA-y(*MvLaQ7{w! zGrr^gJ2&2DYtS~@e}sG=_**>VO+Bl?Yad#V6$9BOFX7`C)$TvMxg__-hSlkD>OOzw zrx&S1w}ok?6`p z2>%=(KPNrzu;yU@b7&E2H@L8!!~MI96`#a7?I@mpE7IXfhnnEa^EG{R7 z23iw(;!3p1kY*xO#<&K`@&fyvr>})7uD9fdCBy2cj&LErt6hNo+^C3L7T|}>$Q-Tl zFfnP?(61UT34O6wyMzK5h>>1bm|=$s(_)wGLrg_*$_F5Zv_%@h`%3!Ub9H!Q@00ZY zQq)6oq{w+Ve&I{<*0o$g%K7_&yCQ@Q@{ND^v*Q3xoQe3gFRCm|PJftdE(oxq3|WFG zng;(kd&OysxgzFXc*cS3E%NzU(Hi(GJS=KZyk1N_7p^3Y7j%N<2z~HA9B(a3 z^h=BF3LOHn7XB7;EIu(|=hW?5Kq3H*8B-Ddilh4$Rgb(D?UJIuWHxyxbm0g5ZcBy_ zB{l=Ou)jY@^M$oOPxR&M#z3SLEkrh4jH^$|IhP~XR+@Q@j~YRFV22)>`S-WD2^kJM zhZFAo#Z|UDgIPBz*hP4w^~8CnmE1=w|7m`b4$Q@r*G&BK=bK(qxjo!nze?RQ*9BfE zN>uoE#>JZYY!hre;N$5d?m?Vd$Not1W=GA*>pb?zD?banb)lw_OtV_BR}X9tmm^I~ zSs38{&umj7mjj;bUw8)HwX5n2q4Y|!99Wa+p$YuzaOB-azHQPsr_0@!((<|3{!h-x z&Uq?cEzSkaL`)*7{qgZmW{X;bZ5=wo_pSK2g|GTYxrnc3VYkPTD~>lS}!Z_^#B&7;+K|u1lza)VYh$qv5Z@{!~&c) z{T=X7tU}-Ks{LDomP)Je@ik;K2M{?qJrsyxu&6E%U3B>Bhzl4dh1|HeeDp{hX zh%_$Q^Yc%q;-zGFe-yzUB1K$qYo8>Pu2m5upC@gdsmG8-Ra0rWtX2W^SKOzdZ0ORcaCm46JhNJ^;cjqfowR z0{NZw%ve!);>@97y9;&mKoMKr5`@$!P9r}LK3Rk)koJf<_@HyY9~spDQK;)&jGMkc zna?S3!LDO0Wto|V5}`AY$P)n#X?GxlVU`u})gK}G!+Ld!@xm%hK)bqmvW+{e#nD&J}c{Y>znHpRdS38KQFe1FswFX4wBAAU7@-pf(dT z%W8hEIH`hWp*Wcc1)0#bk=?@ zgTZSElgfojNMeqi#vf$|jc-bbq_GY$e&p-r9=l0yBRZU^+E-sV?|mE@jt6Z_K-ByKNZ?m9&kFBt~piT zoE)gFw*WY0)2TmiDz>~27&<>p6D`P_bEHZbw<%21wtQRmY{|KIC-Oq06RLW0GS!}( z#r`ktLp}ShSIklg0@@`HHuzJyCHeZ)`TAidQ3$kKpGBSrXdGxXf@*ut{BWH;uli?V z?Kd~7+~3<-rLLIAQm}Ot0P09w2BKvVJ-i}gP0ToLCh0ro_dW)&9P)&SNyayYUrDg? ze01u*hyjF-d!zG41G%P@HScxGr|8Aqh*11J-~%2KV=h#?E}+2B>q}f)5mtJcPg0mz z)VxuO3*xvJw7)149wG5ZI!o=okj)_N*C(h=Sk4f~c<7aUO|ihtM;&M0{GR&9V4>cv z8ZR$R;jc!sYPW7JBvQOO-DD~&VXAFY8e-{YrD7RGPW9#( z=k}>DDE}85v9iEr%<>Wol+~XYl8<%3r!D&dY_TF0731l@Og6V$Ri8-m4sD7vY1NTL z@Vd`4O^Ix{J*v)zw|-YrXa&P1-eb8KrigCc=Tm#H9o$~L5a&~G^j~@dmS<<33;gNp zxd^m*zY_b~CElW2aP3`Usec54i%d(4W*~m{K;9twiVQJLzqwnNV5u@xd+mgT5qvceV>m;+4ZVWqj<372&bS-_I0bgci8YVLMf zLZXD&%+L?k=o4)ww(Hw|Y36Fu#anl<32I7Kks;OBzB>sA(NM{B|3_he?b_#A>KnX& zJ&Kub5-U~kHSumMC{{(Fc|qf1^35Gf^;UZ5+`E3e5on6a`a_*`G*#);BaZ6iSw<2!%hEZnRRWF^(x# z>078;vr2n%@e4yiz5{^$*TE7ypNP7xp)PyZ)Zn9Q$^1X4wK^DD9O=_fWG)0R{)hP* zBkeFVV_1I-m9z97q!Yni6UXV1nr~Sb#5UiJwjq3a{-+inmlAmpb_X{u+$V$>5vWi3 zIF7F*UvIPAQsDUFYX%C2RJ};e>^hjuY&&dk>Ge zrI)&=L&ZOnVmtKaXW!P2{=Wq?B^{iZ)r!^J17mc<+ySR}OOE-cqDJC%o@NmNqcOX; zt|oNn{?+HDv|sq53ZuR_<|a-3XfxNz{u0UvqL+nuDajBh!HVRTMRsCZ&TN;@N?eGX zokPHeaQyzgMr38iXT&Zw({Qw)67&d|-UgcRYIb3so~9Za)B8ugda znC0Jq+%4t~C1cbd)2;w2-kWoaYHQlDOq#n$+EZxe^oo} zYuSSOVYJm{zDr6qz%U0@ZMu?P5bt5Nqsq6%ip(|-HS^7q40}yfjh?euzU=f0Ijfw^ zaDTO)E~m|18%CZ33~C5mVvmKCutkI3^kuHxrgfzH%!imH=ll2SY}Y7rARb;aYasT1 zf{InC*_G?PueTLc<#&kCP;w7Y*K18IypV-Ubs7hYEL_Hv*v!Yp6PjNhNYg1 zbt)>UVHI~Y9NC1jm8Aef=fe_%Hr1v^!^vjGH!5mf2$5mLIJ+W9isK3S@Sn`tC84o} zjafK@f@9?@N>CeM^j}}zt7}RP$ed+nIHcJ2wxZ%D{6o~ zV^Wlod#mMyuODxWsI>hB&%h+n~9v03+%@XHjL-#{|L%tZ&A zIj>uNe${iq>Ti6`AJn*>J;-bji6w?D`&6a1$plF~>jLVKJv>mt-rcj1uu|W+yZ|ul z2UbeKwC>xon_jt*r?Wj5Xk@AtNt6fS)WUhi7)XYAr|9#L!!x_0nSMR$vqXt6rEI@x zc$1Ji#Hl~TFui&-;nUfaH~s4YF8^YhUZAP(NarN(A>;|g5?g<+Cp)SM%<3PIfYS;c z(@W=ydTurC7rg=OsRWK8DvN9y1!i#9ecZ!}*Q{}qd)_OmX*R#icHVYUcbom1d922+ z8#uj=<}K@t!>->~^)fqT{g&L94vhU>6HOoOitYNcrbQh@``a(~Wp5%6{;YX&T@(c< z_Sw)59e%CfgT+|OI~(^#L0)BtZ{!X76okw4)g9!HJC@RmHQsfaXUB25&#_)`Ts-db z&Or>N@-(kpm1_F-vx)jq*K3mFxX0$fJDvAf>zP_B&j-gHh%uu1ReaYMX%wU5A-Tti zCBD(SF+gFeGEF0eiM@TLrx0=0eKtbu_-P2 zja4f7GtwDCn_sy+?MY1Y>d%)wzWBL=hqaeU%at-&MH1%+lM3AD|6;tlq+Z^Jrh0_{ zdeK`i{@!GIoxI}XEO{hhjr+CwRGU2Va!NhrVkGzUeEN>S~TM4RbYxvuI+>{LvBC-gEwi0VJUFa|jX{ObH6 zHR|m2D>bv7eHYZ4&rp^ZulvjCG%I{Um2=M;Ykd5kak(`jFBBaz|F43l|G((zf4`HG zTK79Ce;2l|f9BHGVz>OEr86j0W_ThNoGLNp?^a+JF6T2^3Lm6F&{QLln2L-9eI?BCh?zs}T*Ui))I==_Jbr`q2KbrIW&(*uBcIlJ$Es29DQ)e(36$z zSnIanqNnOa=)@`)b%6Pr5)4f{9JfEre753v7f>{jyvAbr>nZEXTRuKl-67?o2!fJx zs;$&jYi^dP=s4346OUD1t|BEf@j6jYbXNqD_)w&BxL)iJq2hUj5;Pat!bujKAw9XQ zJT76<_loW7NcHSY7nfh+l#U=`HJy+xuav~OURDu!>Mz&whRvy6>+}cxtXR(~@qmsv zt`!!+Nz<;(PpZK$<}u8^$9~cIS@C=Ma&5?-9Ejb$?Pf%u&6@(bH}J4Y)-nI20Wut= zGYrxzOH+qPyYuBn2Dl$){^bOm0R>-B(tGc4Eyn3@9`6b!OJ!gOEs@!#Hq%qPn4s#P zid|%`QIb1$%BVyp&Cun(?HH|24!g*1Q|OuR)Bvp7rA?#A%7O5DlJ{=Z?s;fSdaL?j zdd)tC>8-~S5|C8>4ni-l&J24(ca2rK^zbn}%$nr|yneI`*u!+c4m;L!%TPB^7 z;IICadpQ7O#~DBD)dnrGn*wv`x>)5l_DG0I{&~=&^xmlx!nrm?QfptQOWIoXUgf4z zp(^&2X_6}tNVzIogz0>#abjJF@$-&_mPvv5>+)dPd4u)*;QCl~T*{E3?ev}!!<~XZa3E8qqAVu=>l^O7 zW-|w4n-JQ@l^7k84gvEnS@}pMANB2~v)3P?C})Vm9+*l^rXW&`>9%Q!%^B<_K-4ksnZiB+ zuDzT+=f}GJV^zD-hYvDzmU)_h-0t!^KsC6FA+Z*$pd0ahC&m;0GQS7oyCm*3I8NH@ zuNfI~+)+C=YiHhqi6UDbkU8vcWZ7rl^nZ5K>CU$27Y-}Bn|Es!+kC+Q@X; zxSxLdgxFyGSv7~4M}0xU`KaKBIclpI{x@O}|2w89IN~QQkuSn)^Ti~TaBlgJ^Eh>& znDt*G%yMicXVPGui)*kDq+`!{8+%8|@5k4m(DDjj^-XY<2arvs*-`cE6q9Q+WeCNW<1DgXx5`L;P265U2;;I%J zX*04d*!}uhE?}7Q3=<{~601gnmnB@}SDI9%b~|Qny-v*u4q@MSA|g)hzp(4+v-3!p zO_a=k=Uxqfl=yTe6LJ-=ce%B%G}xX}k=ZCN0{^8QB)x|8^nmx52mky$IU7J1d1v zulY4K+NL@zCyrBScibtL*%O0qXFc!_s_Pq2h4+{^zGwUdd3^}E3Hxqns;<8h$DigR zLIx&hCFnmky0v4%aX?5w4J3i29#*bpFE|WigIF)!@P9DogtQf#iF*63J(IyRf_D3Q z(GTeq%Pe7ztI^*X;g`}pWvcuz|g@5I*VP6Ma; z>xBJS%|`zUgsUXAcCX^)(uu=SdfPP_{olus-sq!y=?+>FpW$I|Cq`Snt)EF7{nhzA ze~;=PDg>bbU%F73eC0n<=3>Np7#%|Mk_vUz55amY7)8>}pS6{GsHmjtBD$?*HCJ|J z?wK-RqxgDRh`3)GoMX>6Iflx#qU*T{`h>F=>Qao|W%PxPt-cFkZ%DGrz7#wTz^{~A z{TR*9Y1BAW5`=#11aYG8buDYsN_fh2_2#8BJ-(lt@S4;lw1RO&Fv3mRkT;ar9F{@X zT25>q1)WXf`qL5y15`ErfnoeA;ajV_KW`Z^^H+PtHu|0%^MZ7k2wUQse=e8m;7^BR zjaOxN2)U|H(T20ihu&_-7mY>h@)r@40zm~#pZ}w9@|R!kNP1~;=BYN!$$z1~XygpH zChrFnO?jlDuY7Jq|Ijn=KhW|==XNgn*WUeq9 zdwMs~7+)WN8_nHy(X#w;RTSFT`W2bNqn772q%%Gah3#`vcb^5l`BYC5S<69` zz2cc-H&+J|@v6y#3hhvH!HFh{ISu>d`WKMHQNyI4@YBwrPP9#AnfVlr;Pk&wf6|j4 z1CMf_nTfK+9j{K(HEyIa8H!w9g9V*tT)9LwF%vG-X?A(RqXlE7X@8;?ByO(W8uL(O zi`$<%?#MQOWlb<0Xp3M{tQS>S4p4>Ak}d#zJpv6-k*z?rb&%CONptijk1lpG+onXN z%>4u@W3c7k7$+-n+16z>iZ*Haf5>{zsHVcU`x8Y#sUp&(L_r0K^j@Q)pwd)Iq((rR z^xguYNG}l)P+C+#nh*i$CG-xV_Z~U~2sMEu{_{NZ&dfV&K4dLEWd(8$=eqZ`_itye zzjd0OM%@iiF%CC$J2Qtb4~To@_uo$129XSRA(x=m2Nf+So(9Pky~HZOOz@3F?47a1 zPHU&nmwzsQ5g%l;U%rW%@=)_idmZaTO?uAn)Mcebgxqaik%rnl>wq#{-m;i2iq|B|S#bVo0z;uY{Shmwa)mPUm4?^dq|G8Km@V>^NU&ZSjC{Q-eKrRl@F`z~)LPCiP! z`FBnBy-hOEyEvAv3mw3+ zPlQCd)Vxakdn4=7OE15R?RC*SQg1zFKiEiYQiIyy$c^_l+Fq;XfGvqI2Ub^|OqtiU0kU%GyI#_d} zU$+mfn{&d%hc@%SywJ>!uheW-Z#_?IgTNO6qoOG#C!(M?rZ0XxC#Ldi?Sk&mFG+B> zvDC+sVj5BYNU;&iioWdUUEJJU3e;oXr$|#$ZIdiif>c4-V`s2XEKLVy7B?CwtxWSv zvEE;F#G2VxZ4v?B+~huIDIhc0USkH+pG>#$#zR+cJU>}!LPi}Nr)b80^cO!3Z}(~ zZ+qOUjn=2|g~NI;_&W|zE4q0HZ7aS}!2%8y4bHh{-&!&4b+Cnf_5F1%7W_#qySy2?&MLN4%E)oA|S-nm)Niw_>{U^#!_1 zjKE6YTrtNaN!Htatj1yB+hN<>1cTcd^4;4VcnVp(& zwpoC1=&%iZ=n{#0-z(@oOwjQ2Sj=k09BayIJk4EiK3wd9`4bkFo%g@8yxFk5C;Mkr zOCI^uJ`-DsK-@IOajEENH0ju_Pk6<$NBT%$|Jm60FFzu68!xXTayEnSp&IpbpRByi zr17W+k)Ys3 z)d*e8@k^RNC39vx9Zh~&6UWI@AxZ7P_-Ih=6byKYH}Oj|sV@=SX+rkBcmr2YIOq)j z<%jILWti8)@rK*?kHYN7o#Q2MEabah(Kgx-nz7>>YIb`*!s@28nRWgs%7g%Ho-j(S zmcNA^HuV13!mO#0j6YPBG&fv~b{2IWXrlqT{IoFW)E$oOnk)%WYz%f3K9y;)RLz?1 zGlBFK`C&}Qi~;Yfo!HYp8L~g2(B<~HbM)aul;Jgos5b5?oNgJt=JO>2bLBk?lD3gU zi)f*Cbq1qImj~Tf9642*7JD5@MWA~bDrPnFZvM!dtMuL6u1M5(z7P4QUowtdYP)bW zh?CJJn1wn3D=j-)s8<9S3{61haZ(Dmz*hg+a0WRMf?q}CNcrlgy; zk~;tn<4{T1JG|vX?H`ip4agw6)A`wTr$*y6{oiWm|l(q31}S-4J0f zc02c=v<@mN6#o%`6OG++>aS0W$u&ySxApZX($lu8H6i3o{a7}8>AjUv=T&8kwWWll+_zo#)W!L_UR}bB9do~< zaorBkSx4lz1m5+lsuR28VX%Lg;pir%p#&2nv{l;}8)janvmQMrBO-;VbIT48F_j4B zZ42P2QyzlSiHxY46Ii=*p-yn;RIUCqquSw7x!|BC6U5VUKKXG}uPVzMS>aNfHgJR` zqlLa*WR*XJHEdoCcMi!#s7ziFUKy%ItY*dGaudyS_y6wOZQ`c!oV`phaUwD69~%S4ZYYCR3Qm&6&U?`czfU z?4Tk|iWSh^jIU;iarMsza<3Hj9?y8F!u#HmZ@-x_{i0}e7j`O(L6sSv{G;mH z#|zzyf^Wn=_eQd8r`@+6>yEVYLA=X>EnAfQat!c|mQlC|sGpI`lrw~zlgD$nJYM$K zSr83=25XaNvN_d+@Il4xMZK@JL)iCfsM+y`j;@Y(@7Tn%t%a$!CgfT#{sNx+;y0Gx zkBi<5d)%aW>CH_We|iPvhES&owVz#kTDpOsI%6&nu!r4&yHt`cEv;MY)LPyTR$NKs zcYocX`77##qAXt#C4Ha8fOqTVTg0y~3OIsT^1R~&HyjoFxLOGMdnuOHnczM9;SyJ` ziwwReH1(A=G&a?P!9!e^s#Tn4&tUvOA^~Ixp9wc3C7>J<=rC#tJZi}UO_k2|q@Wxk z@)IcG?Pt0(M-nrXZUuhL@2Euy0*BK1)%}VqoMk9}F?K!gEh0_zE6fKag>(EU6w+DO z(6kF5hRUxLSpWF|^xF(}BI=MnHQmDlA%Td9Q_1ZGz!#So?NFOI6%lMwHUHQ7S@u@r zU^IwVwl?R&C&l>}F8p5maCdXyce(@0cD0bf)X!*!gH@-8pa~LF!vRbV`*yyQt=0P; z?AJyHHx?g$@cZ>Q^c*yXECMNcp{Fk^^HkO0e*K<&{Euoap-Di!3y1Lqq+9cN5tY+@SqvNI6i+DAj&vLtd zpMr;*tdCgplF=}QwdmFJDv2(F!K;d=U2cQpPRs$T)_F!TF`a1lc}FlQyA?F+-Uho%p^i<+q00O2>7x%pzW{ilvk))wOVAri zYb}Q-991zJ6~+x4hiCF>Qtdo;14mwvFJghHyRf7|$T4GxDNv#HzT&2S#U5#z^tl$1B zN7e9lBcMW$aA?_l2*$#eY`DlF3| zYNYnV{-IoBrRYqX_rrtMdgE0ZKt4Yre+@FkLS*Y}R`#~v^AQk@xBuuu&24rJpyWe? z`L?7S`GHk`Kk>z0gPr;*kbviYq>n@~nSF}w%jLyIRn1Z{(maO>w+%e*FyRJWUGbzl z{YzwtSfM8pK2qw4&?6TJwzg#*=Ck%2xKkeE`i@i z8~*x;4Tw(gm>TewYb!4Au|guoxaMj>4a;gO8k72K2a)EzLq@?gDWQwk)F9;yUa2VPb={nM(m5Ox{v>I zs8Zx5xR)=i3)tCJ7(h1(G#V#5$|Chl7bRjhq%As z6T3_6fNR9Au5X$Xx3^I0%ajrbp$>^S65w%|1-L)eM%O1Z-u)O!u|V7D-God(&2t{l zH&k=jE*GY{1YcePuT{4rubb?^I4WOwnU|T)0Pd+LeJ(t7V;O?IJWpHRPw0gIG_4$H zjkCRHxk1BgT@+g5p7=wv5vF@Eb2^6ZSJ7Rv0}DkmX9fTo)OhfSe1{nWX)ch*^Z1re zC8^etn;JuIw2LEk(@hdH&pcAWk-~hPI}T`i&G{KxDKq|WR??98a11>9*>p8${N!VH z?S*&mhOxZvpA=3+@!(D0-7;L@vV54UGrR-B0&9J|oMnsd8PCnq%Wzh@EdNMuPY|)R zG1BCjC{^bl%#OLEl|6$>Q^(t}AN%Cuc*l=xXZxaTirAF%_S*64&@)W=)=AgcItqEil*;DBep0j;301i2uhNsJKzuJU6gAJt*Uh1Yh8Y!fXVYG`hKR}>TsN$ljL&) z_7rO$7w-B_oE=a{uF3sTvExtqv77)SvqJdn$)aVwmA?xcVCBL3T|<-C@6Y6<7X}H+ zRtO|?l!ee0mTK!Tf6!Iad?rFn&)G+^Ka$a7gP4>DZ@v_YazOV8tl~~&cj~G`yg)OW zr$M-==6QDmQ@=Rv3_Tx9)Y>PhRpxeU!G#8neZy)TC{#syWof}$>q_e9x}%G}V1#z~ zIk+|$UDse_vN%mmaEzDJ(ER!qbtp?*RA*B`DQv(s;6uO;!{&UkF2Y*}cn~F|bKn|n ztZzKB>gbr^-X3O8+nNDuAZL&NP8-S?5mKYE64ycGAYY z69@TJ`Y-s5tp0VfBa)@C$nG6wp!&6_9I@YOcvM{giZ*{ zwDNI6BQMD2)|S|UvEGW|ray6KC;Eiz-dhTBIe!4clQq`vL{ugrgu*)hmIej?( zPM|3z0}peGoLG4MaMyaaEPBrH)eX+X_-DaRc;CmPD$*B_f&(Yq+|xf=cl@&68!n&} z2CowIu24|!=mW?poN|gVCr?7knc)ZFrvr5v&5PF?*vUT+X>0XBg;MG=SG%o0d9F!1 zvCbBdeH#>)8hM`JLy>+L6j@3?P`SmnNDdqB`7Y#zf(Y$y=s<8~MxMEIgV|xcQe$o zYph>UBBBnia{_(=vv7-vqC~o71|$c?_ZB=pr zOYye~crfuA@T{DjUnq${@$LSrTi?;WDl)^rRuo{$jM7~kgXuss) z=@R?vZh~Yc)zw+FvLjZH#&4x<1rV`<^SZ0y0`AM6R(d9~iaU&_rItPR7vi`uS?JYD z`MOTHx!E@Jz&_yW*cTbeIcrA{x^qeM>^(C}uQDbcyojg}ijzUJlKZpe@#dvw+TogY z)#Egcjpc2wP8@pT((^P^^>GiVU|y3OPfb2vWkdi0U&H2=`GaaVW}2!=u?^syDf~sd z#dX~Lz{aUqZC-D(@oeka<+ZxVq}S$0{FfkUyxHODq{+y+;(JmuHqY;nysQ!jY3<2Hf3aj*ZLw#S163B#xbzN6%crmhjF4Irk0^{3y&$XjdE3`fU^ z&(9m$86~dS?1w4vNyi=`XLs68>(W|&cstywtE^1C8)h9}_ubIQa@@3F=20gQ{g28P zeghN(vc+C4j$Y{3v(3xtX5z`J?P>xJco|X0=W#pVV{^IO*#~Ipd9#oK3q7LiK;J-I z3Z6X&w7a{B(F}~;XuuUjgEF|Hm({jOG{Hj_=9;brdN%wLfDiAedlQvl6|qEpQmyT< zx2R(qzeM@irHrWf>D&u93#*Ptg5S|EiSXDw;qG(j71w)Qr%J9<7pIse zQZ&rzg$j&=pu)Yv%AQ?mKk((Sm$933W02u;8~hXa#2rU>%1VksIU<=&pid4?Gx#%S zzE}mCL)8CKX&!Ng@O^(G;Ms}J3d9Wp@fvuf{Q;Xo%MBa8Z=NAV%GQe`C>_{RD(b*c z2X=aU0=2A9)b^KkjSR?bI};L@qgdhO_rF9>S8=?POA1lMuoKAMZGCKEr&HcVs*Ket zr-q~i=Q_Y2)OCu9J`r?FrqU4|uBKEu;XHcVbQ@RFut{erxA_T2ynTzn&PzWXeSRp+@B24gyjVu9&EZ-BO#+ zzEoArQf}c}`;gC|zqFO#i4=e8WN~k?w`WJL&SBZzWpqvF%*g@Ux?@_|Ks5ZpVQF=# z{Y1Qz%yhd)SA0^uOIN^ZgCTNJCs&8}h|u|{h=uF+lFunpdhF!6H!yxqD(%zWT%!~$ z>Y#|lJ<-2rVo4T zv{_?Tdh@qt-cU0{tcv;lF9|fM{&07U&>{QsC_YXVPP#8DAZlUP4rUzsvB~i45Ov7T zc{YE5eLfEN4+r5|D45MkB!BviE##H23CI$?F9DVorAzZjC3o z-oVZvN>GwgN=NA>z3K%ASg7CnG;elnB>FcjXVeA6JBiK9?D#QbilnB12|!f~F{iGa zmD5|d1qEFe<|Qq3-H9CD%b;j@Ul##JY2O|%uAEk;QXV(Kx6S@n-g2{g@b}f_M0XR#L7F#(BEja_Sx7%L);_pD_c)2iC5zV~Z$J1RK^#)uw}xK)&1=x! zE0UmFNXfcj>p6FS#EbneTJt}6&Hs6s|Dl=_cKODx9cYx0{%I-yu%z%ds0f4?(2HCz zJ2;uBE4mV_H>meULX(|Dos%qBeHINm;B|+@Qg|WH`+(vs#JgH)BIRaOF^Lfd**V*e zrxNB`4U(`gmle1+WY20qKA$x^RzT5%Ww!Lxp_$`iYAw&WGYbQu}qA0*#XNp0;AzSh91ewk6Y?-Eg z(ZP{PgC`|YJLS;k#Vrj;7X!o?PjPV#pj+XwX~B%(>u*1pmdZ?k+(W4)kt9zcKteDN zz;JoK}(4QoNb_TTYrGVi-O-ho?`-twTGchKm5LXr3J% znmn4N1X?)BtY}VTw5T;{4h1CNzO;bKu^H2h6}26Cn|lK*Y<)4nt(Vd=KWun*DVEqz zDpKPpEzC1>XnOCd8|x!s*hQy8HiT2**qSjp(Wx_nX>sKRD}O%24T2wP(zntpE|iQ} zD*&_uzk^Zcu36S;@>>jl%29ZeuX2p+LC&Da^v(x%OTXDDl{yh)7DBkt zOM;aN|EPwsi-mI+rb1WjV#(4_AP$3pgsHIm9-OuCON@C1(PBe0bYgYNP!=mlj4ZrdscQp70uIEsU~^Q{%iH?}OEESxEpJJkFodD-%;7P4app(GV6q`E;HA z&)wdHPBolfeqPZ0LEO{StCqEmS7=?6Rh6>G$2*KIc*MUKbSkB^_?HUu8IFHDfrmmt z9N!#VaGl1H@{+1M1j?-w7KQ(;13n$Yb^f$jq=`>8+Uh!Z=D<|&*ww>uAJ5d zeGE@iM9*`rqdo&>sM{&A*V7Q(M7123-fun1BlnLFY`2n-4%a^k0DAe$Hf`pxRv%E+ z94jc0XdE40&-OiZ*QM6jr50XF3_h`Z+B=+iNVPS~zThV%d>8i&F)Mf^;PA+1*PjcJ z5=>Y&WY=fc6|Ym%S3=x1JyfMT{q!8`4Px~3=woue2^6?=1p9q8;B3pH{z7`QxQJ*m z^4q%n!hV5E#@&Bkq4i;zlx7P3jHddE&C0c(f3|YQ%-URNVrnI!iPQ>FL zc>nt0&gHh7lkz718ZT?6*9yv^&!H2=26o>92Ggxu2&{t)m!nKCqA2l%4j4Bd-KjHb zm|{!p_uq;I_xI~zax|Wy6Q~rX`!7tNeoWLNBy`o^!XsF11~T77 z9sUY-J~k;7`NkJ;xO49xRc^U*e^6O0X~7-dTHeQmx5gVKObgh~?~{Ocq9|~K3>=ep zUm@v5SQP5;zL_F#b^p@40ACXDFW&fI!yJ!XnH9gZ$$<`Z4?6l1$9>qT2W3{8q^mQq zde`Qrk8qDYFFxhQ?S}^Q22ZHbPc#Xc@#rz>dGjUaNHD78zMq1BSrus-nId0zTz2Ey zzKQ#jDvl2b>O#h~f&Cr?K~}1=8(F47w80$+7SSeF^s-~4bnmz`JoSv?RI^5;**k%c zDsYfyOf!h%MDMEnTvbX`zTgb19x-!tDopxLX8BOvB!-t=hGUUb+Y((c*Mb%wetX~V zQqKhH)vUa);$HBp9f{9(z!(?##ZQ@d)ZOIJBuX``K-mZl@Wy%Lr?L&U~O~&*} zGlnI~KU_WY8ijqsvxF$>u`WyhHW|EkQS0P4O{GDRnv{x#7TA3}R0iFXw+wPGT2To= zsL)Bjnz+~RfN^8x@&5No25YLt-R&Cdc@JQD8Xsx6ixtq+1cNeDLd7@tzqdOn<|Fiz zhz)i&Z~gh9bLCU$&|uE*K0#PtP#HZrw~1zpx-FQwawK{Gi(d167`}*ji4;$~xo{gN zmTb@FvMx5or9g#9N99mjMrNn%t60spg`DIH&_b?Bw7c#*BTZ3T4AF0$axNu}T)f;L z!apoctLi{sKevZQT|D2Bo7wN=0Wr;@GC1O{Qu!Etauqu1u$j{2Wy?K!&XrANkp=8| zNPI@AjX}i9!ntQz=C;8+Ng2wqORVTW1Q`9X;|phO3OLPj3Y^kf!?pa!4W6_)Hh03q zc9R;tl|ElFPR0M4=Zo)g>E(%)N9?6;U>GSEA83>-RxNz#o!sewtClBypL%5~c0bzb>m6RX zKoU^=%wzW1%`3k@n5L4&^KA9*B&FDJy(QtdUS-r?Q5Wgry$_43Wl^x?IWR+R2;?<3C-iv+A-)v-N>uh2~IaCQNbqt|iywmYA{fe$12$0h` zoF-9_!Y58{tMfnHS9$D}x{dObY&XnEBsrcfu=k6~BoQp+g$;{=#~{()`GNI|e-{+l zv=_Ye;)(sb>?Ax=ci5%$ zQm{x(zTA|Y-?9SXF^+D9OHD{jB_%$e!<2%iU1(s&ZVN%0*FL@}f}(C3oI>jJYsFjl zBW^)Yjm|&*m>B!56BCTHk{rEgN;&EH3$P!)TXij(wfQLqAI--y8j3gU{;pO3^y?pw zIliKz5azfpIts1Q^w$*oW3EoMGxm-3Oc?!sYJMxcxhzvCrbAKJE{{Bjx+w}(%%Kpa z)nrUrSc!5tu~|o~n$?xm-S5e&RiN?s%nNobBn4HyxNBHU?hBaXe{gUzmi~{bZNMFh z<$N3CYvWcMW?5UMlr7!48_aO7`pf=PhmWh-?`BwtlS;?OMsJJ*Kbt~c8P8*&-Nh|us@)7e>IWdZZgZb%c^ny z^YmWqknjW5itB9Ddu#ORMC+Kc%&&weAO|yYa_-j3pX*31?;qz(`DRH~v|W7zzBA^X zs{kxG`R#&yYI#4E^&r(&bza{p6beD&gBP;i>=o(twUk{A)@bZBb?8yQLd? z$4JSthD-h%vLmw=rG69%ey!#_m~b-nGCezr;w`%CRl;?`oH@zBzRvWa!t|-B*m`JFtwr`XZW1u2b0&2?$mRfGX6{M} z8bqsbOmp61@8?WHes^y9NA;u<3t;xH{yT}O(Y%(b8q{-4N1tlTy%QI(TW<>GOQ~fJ8rpK7-u?FZKGvV9nq{l-3dI$v@S@V)_UJ6 zU*M?jChn{C{-g32M%YotVgt}p27&`-RnNrIuGATM=in^HI_g4cotF9M^Bnd2dJ8-; zXxT$_RQrri_%8>~x_Z-DjRg2U<OM14j5P3Zrvi`49{w=;7(UR37 zyTe~y>H-^pmph^}vDlYzV%mdxJMR;+Tn!e?WY z>m2+GdILXPMC8EtEhFMuukK@ywK@W$FLS0opStdL_4Xe+5y>%n63<7^I!cvv$xC0Y ze3;;dBwq1_!l)048MA_DTf9u^gzWsWM6#oZoiW*&x90letzrfFbw{}6rP*69n!z2g ziW{n^P+6TEH!V`(KPo75b_4jBGR@G4O`w)iDTR2!H_d~GDA7L&0NJ8BJ_K{&$8&V@7J6)$ zju#9EI}_wvqdSrV>8PL!;WJIo&EIyfV)&XNJLbr?d5vim?>uvhkT=R-dtQKiK2lFj zF5rV=;FCW9>Pt0)DpS|mddQlaXe=miW*czLI|JzW$oY-&Yil8BlCzVrl%+>LHy1l$ z^n%?nQZJJ8T+)g*qu_mF%?;`<^{tRV@OE`aWz!gusnl$1sB`!scFk1r-FMcko@_nL z;W?$e#)tH&^4Z8bE;y;e#pU)VOW&aRUeDcYNWG4J(70a)%^OuKsQfUB2P@@d%Xggg z0hbOOG`Da z-cul_909QO>696sT9zm9)6vY#g7AyCYwQ__v_Hoyb(9dTuOa9FozBCS%w1~{q(Whr zx8r|kPP<<&S-48Vgc%gQ?^Vr3{}u)~?hC#c7;d>lLkT zEZRXd)@Hr;bwg}Xj4R(f%`TjH{q-`{fDX=qzj}Dp{qD+-#u8%RuD$R%@Qm2_(Y!^W zMG=8}ESwI%{ES&OsF0r;40Cnt6s;sG4-{#gMPzMWmZH^}@AQ%K?!8IfypTTyP);(j z|GA$2AYue-@qs?x2qv_gH-N<)FxP*xY@WMo?|?6=67~*I0VCdwJAF_3+dzG9F~r+o z8;p1-0?_hw0i3rf+;7IwXe8#_O8y`REC=8wt5jcMa^M6B^{1NTyi8W0ikYv(huPdA{$i z$g9&SK2%2RQMk$r+~17fEFF8$Kncqi#f=)zD(n01Q1cN~DLYp!oiIMR9&0$BYNZLH z*skHRYR&??`uB~}fTlq#kN%NWJ;1Gij!mOH&9kue!RqT1}pL3i|_?a-?@}xAdERE z{^BoU+6_G$I*xJ@`Lh}l46zUqUbA0$VZ~E1dr|rAE|GTT@u}>kI+2jhyuk4;Pwp&j z{kd{XKogU3w7j|x%W8`{?3_pVYm7PnV< zX&6c%X428_FxKkftjz9V@F11f%77V%2+vY z*xST4FXxXzSBa>XR3&m$z%9JjDR#^7Lx1jd_wJL81CmH36{R#=hB{3-5?C-i`NICL zY2+8PbxEp6^;gm8hLcVut6EE}IUw>2-%IwTeW!QFrJ3@bLS-ZhoTEjCD4RC}r7Is7 z6S!BdNVhf{xdr(Jag3=A)3`X;3`=`JxPoqD#Cm$d)=Q48nJNSQz;LBUR$nab?A@LnB{ zT!D~lADB-@+FOZryV{q6`A-2eY&sr+PxB+4O`FeiuL-iAI!_C#ssfKS$eQ*{HHzK0 zeRd$%f`@L|3>N-0o1UqGdsj^>4318oPgZ{(JN1i}9nKjX_>y%^T!;Wj1h5dj3yBYL zdl5MG=Wb~xOk=*UQ@BOB1Ou>?mIO(5G91F(;QgIwbf6o9lW(1T5(F|UPdt5~!L8TJ z-lRY4b{qbfBDu(&m_BQmVKw(rN>s}F?$Sl65ZpnLuim-PRmVx6x{rhGu))vBV+V|( z*rBYoT^AD#Mg=pc_pTPVP_DSR+o#jcysghMD2E7Vqs{~KCCLLB@jY!$-5?6 zaFt6qq)&{E`%nzj8d*;|xgyEjES5u`=XZ`^D=u)$qM1Qa=I7@9bOUKimz%=F(ZMMs z&<-Wbs{;A4=;zw;>fyQV_g}y-Lc*`zkHoe48Yp(vC6-P|ZQ(>gS zJT`V%PTq5{DmCT~Wk9rsZ$Y%6qNME76NeuSI3>@ToAHbo+?>xps(MJFyHNC3Ms^`t zeN%^XK)T!h6^x{8V`kx|0~rlr5L0>Vmn|^yx@5_)-+hC7V+LgaRn2oq5B_#jo;nO{ zC$!e7e31woB=Ri9Ip63NdfF4uuG6S+f_zH5XLC45Gh>kH-RVexDXzTymUw5ZxLW<( z^uhABF;q}D^;BHFWJ=SgqUGAt_&-#MetZcqz=A|gwkr=lmOx;0p4YQXSd<*Lf@NJFoO9O z6x913?ODa#ORPKr5J$Q#$;>MVgbAosQJ&=q+vgEcyTwKJ0ni`EJ*TGYu*L{``7#byBO9&Gt$(ygn`dpWV|_IExxvv$c8)(Wou zp=P|{c$D#~v1Y<$;qXwit|pD;Zhx@t60=?~-Rt9`H!p+<>el}$h}Gw$l`vwB0Hr`v z%nUF(Lm&UDrW*(#N=*L`=JcR0ImftD1+Xxp9QMaG zEcSysI823a6s(%h~zrD}{$@hGsWPk{0sEql`TF7z1DZd^q9@tU3~pMovk1QYg4P(_A2i6^y z{a3}wTsfbUEqC@MR@@|3hT5VHd}0v(E+*Pr59!7#4QsClXJN4dS7c|q3J6@ zk|K1?JYk5ISqW)w_Wwxy!II~v20yUV8)*nCfof0O)zPz(b$t<$eC_|QWQAkpga;ins-Vq_4(5=iF*eii2 z6}M%Moc>Oqpeho5(fs%@e54-80~5Q;5T9L{afQd26>d>N4g!`8i%y(A;Ep=4nxZSE ze4}C#DNVQG%rGx4N1Fs6;W?f#;PBO_cp~EefntgtD z6v^HyIwK`KC})(F*ZQ7sUXfS~vI%0ytkn(cGT`UM-A`MwPjgC@zusLEpR`uBxC8kP zVZ&6E<6wZ*1UOK^} zU4Y9(Zp@vIj-TcZqkjk?Hs7+@1xX9zczC?OU1h5XZZil*XUki$s@8abjPzPeL{&`2 zHL*BCP?b35G|yjB8;(kS3{R(3zp|vFdi2dk_SFC%&Jk_1n-)L>)3$toV=+@{_S3va zXFPJyadHKq{2x`UKs8P;>27R6KPUBCSFc9^;9?y$$Ut0TnC?)p!G5XL8@ShyDo2Q+sa?o|Db-Ye ze^+BZpWue}zFTzFqebIE*fBhbz+K-4JGXPgzxDUrnEd;}UvEzo zeV}qT*5tFXj~PBxu0+{7acqH|_s*ehTj@vmeeSQJqlYEW#aZ<&Rh6w}_un0fITU33 z2(h@92guyYn-gtXbO3oB#T$kenRi{RdO@^n0EMMA-mD2R@qw{!+47Kd30O*S#AmC2 zR1MHXXY~|MkF2&Ar#asQs%%yLUj@aSbba`?gy_BgIU}fd6=Aac#xLXn?*n)r!GN)9 z_K)+cB=RVD^P8=pUNoNADqpsF)um;&(oOZQne`^nQ^VKXqbuUb#JYkhD@ULqg{GiA z^2)i(huM+)i8H73KjVe=FA?5mEAdi?j-afU>5I6|J&x7rl=89mx(pQ{Kk;OPw6Uok4Vuk!wyHO5@;GGUk``^__ zbcpTx8_&=lboshBub^&_xqD8<6N9CnxpRJF6B|8Zi_xUI@J3Ry;fJdO65iJH!J3#w zjodFsN^cH{XmfnEZxMtjj#K?Il4QrD(R!z=|Ak_QU2wlrGO7ir^>FF2%uY73dw}Es z^}bzq0E&Cvd0haMe-J*3D-}9*s5=b4*%g{;)*X(say$~}xCiy2&p+cP-D{Ox>)S5) zMBV{fQ9XXmeb32XP_|psKT{;D-mk>+?i2BaG9lhekZ>I1zUS_rqC<-a z!i0v#oU-qh1uMp!S$Xrl1Lrz6Jk;VOAM&<2@sReaekM}~j3qc_@Ixv`b?yMS0rNNN zQcATWp6n>ZZK2BYt99sOjxCh>_l)Y72wpdq{F73L`7iH0n1Vnn2zrL4KJM2Pp=KLN zJpq+3aOep?iakEeeh>$*_znGK)8_nQFttnR{&aW`sNfu|m$P7DA>+gP>p)iyX;^A8 z`eNKw=z1Fim46@oxe!Gj4K->b-N{th;6;3a)hFT$1}f^@qiRJ5${Vut|7sTDg(&1w zTyA-l@t2_O<17o?zhw`)zRthQewp})`uKOxU1yQ$H#Ry&@*I3S*LA(0%uq0?^%@il z%#`~AO73s5f!bb**xQ0rk(AVhg$ES~)tZy|QzrAM@Xvu)ODiv^lDiEptM5fID`m>_ z9+xUwY~M`0Ch=K&p34R@@Ak|bH7=tCsMtK%+FEb9G5g0GUxW7 z>MMMZ-8VeZ)^F!TLK2xdt95A{Hz%dU5GmYiQ^4|TQd z*tdBU+d2$T5?FQfc57J_!vN{Q=9&PgxW`Agy>pg;}$olOfb%nzyQ7q8A0? zR;}ppJL_84LPwr~ET|~|lj0}EfJ?#Q9~D33-~jC(ejb;fmHJTRz8gYm1?+>Iilhf# zJS^JNcBeGxml2Xx=jedBqhWSQ6p{&|v&_nG$sx#=u6lSx8ZmLLb zIr1^3;6Z`-AWh=o)%y3w@*>rJv!wJ5?w9{zpXJ40S5KTT5}6pv8~r}vY%`8s*c-M{ zmtlP_;BS4!<=Lm4j}IHD%gRWjBOw6MBPIohUkV51-0~m!?QV-vcwh6F2(0nzu#x|? z@5YITi9xN4ETD>EfjaaKGg>@>g;z!&Ge&tlI*ibhX$ASN??R+qurXQ1Z5q$rG`YSr zoK1jJZ^%|2JKv0p&q??#C8cIDXXI>Blm241KP3)eq$q0?^1qXbpnsH&KSh+u90CtZBFj~C`cO7q}$TD^rjAe z%@b*?xg$k3F14sfMAh?jq_WoS=}W!$>Ga=I=yJ&_E@r@e8^?2#nu5Cng7Vm-q*RBMn0zt;@4oo6HeQ3ay5Ao5UpxXvHEot^WKjAYyQqOSKwM0(aX;L?3! z8EAl*h9n+bJ+rdJCwg|M>eKMMOzSX{1ED zJBCFnC0&z}?iw&QLO>Cih=hnB0wN-e?yk|@J-P;r94tQnegD5Wzq7NmowIYcvvXd1 z?-}<#?ozMh9O*ky$uq;sBYyCZHsZ>!$9L83g)zC(dCA=yfjYW{Xy|*a_xeX~hvWqG z#H-(7iGDy~utU{i(ULW|!Rem1%_fu8jXbMULtla_`r2y)5F1L{ZA))GEH_;8w?C;H z@JxlN)Df7HuOl8b0HMGjv}_f+&6%Ze1xnXpR1qTk26GaZ`O{D*gN696wud~|vgzMM zYr=gM?t8H?t#o0>w<-wzaCHnCSvaAp-I#bUI3+`ILya1Yn^_6^t`)3q8Obfw<(Qpv zWbMj5(N=|#F6G$aHE~|dI0t;id|Gt6UMw;28a*E`p9-`M&?=t=D?O>7xVUGwKcA93 zlXTLjsqED&wOSaeg=mW;aNrAYw+WPDwA!{W3G`Gn~Nk zC%%rN%1%&ag4p^}+;8*DsV+d=4fkxb%=hP#9nbt$5T|?VtJDWp;&mlpMNEdX;fdj*Gl`VXEdsnL47&d!SrU!DsN$*%>emBdWTh3lDy@~kRH9Om$GEo)6QIT}j`XFQMoLB<;X zKIEn*Ija(%{X{aVW9PI?3uVsMvNd4k^xQSa>2Q_zR-G@!GP_{%|7fSzeKwBiIZ0o9 z*4@B^D>epSs?uoliG~F%v;{eL=b;j( zyj{f0cO)CZz2+2JcM+AH8tMlFUJL_0bg<)!k`>Kt*7q6iwZBMo`+i$ zJMUtH(B1eXDC7cIc??!r^xlmWN)B{DGRo3qV1OfMo86g5UXNakRmbReMG+Y8RlhBd*cREq9A!w*)k$or0)M^vk0en@eDa-?cQ67!j(7i;()3os zy>;_r^kt8UsvP7^HzR%qYCM*Kj*0TX;!UC&MCQlj6xK#O zS%6stmi70pBFmmZ*oSj?Y>#*N?K0#U*L1i+gNF*nrxfu+th#~-+3(C1iYE!*uGCTN zTQAV`qyM>h8c=h}=#n&;(3vhASWx(SzO?!MM9nwsF*Y0nC*03NUtJ2Fv=P}KR{OiT z45;|D;$7*`4@2|pL>SrYzxp9!klV**L0=@k(p^2><7U^|d7T_UHK^7$EFoa?H&Rl? zo3Bnc;I1iDE;_AOCeFb+weJoUZP-OsnQUpMKi>JwKh=PYjabEW6O1xLpJVEY;ep}A zSrCkJ)$X8TL61(SC5-8h*mosUzkxYrB|WBBM{M_0&M3kXnW+kLQW$5uK1@41bRM8*za$GlPcY^YcLBi#|DzH~Ju9(LhkMcnI%0EGkqlKx~Szhs_pq^3T$?3}#Zb@YUM z>9fdLRp;sZ@`qs0=O8hq)kIYYJ@9SC&4Kl%s>*V7LDRgD?9?o5OxCFk` zbTCQIiC?I3_$;ohP!n*!T75oQb9^$(y&> zqr#=ddGO)=lxsnvOMB^Gs71Td#h&$j6&L!?^Sfk0hUsxkOd}K4-{5&z4vUR+d!jr& zY~snkd;>v|tyAln`tR&jbnm=F0!Q8Fh>Pdp7*b5dWD(N=vcgt{gXIJJXPbUinU^{L zWDuUo0IswE{q*(acha6l3jMJJCXV&0{FGNli+QZXWaS#01kcCqej^@>UGI!x3yNH9 z66?y=PjW#nhSHL^R3hzGb@xig;(C_?_bk|K)P0ExYjUP0*Ir4+6pY!04D6US;@Mq{ z7QJRT>5W4poT?-u#|{y!aam^5IyacCSeD!Mox#8+t^Xgg0o2`(m3PGr5tf9AUF8UmRYgUSY$` zrmI)10usY#j1gyAIuAKi#T+X?ern6SuNIH})yS^*uWV(#zHf6OTFa9Zf4pZcKkE|L zD}$)HX>NiUS9Ou+C)c5MH1)YNrS4N;#lO^$}gWo_HO zPpu~jI7a{q!x4uu>y&B|mkHG;_zt?Bbv?_#`To5x1wy#{>#ZWm{>MJ)!=Zq_3u)mM z1bwzaV=E=-J659SGK6(ui1>+NBm z)N#w8yrycA2bH>`Gda=rGnbZAt1E1AVfCHT;f|DiSc%XvYL!3=6oz&TLhk`LLU8nA zgeUU@C4MKz=2g?J!0%UN@9BASRzqJy>S+Cl4IsDSWb{=3^km+fzJ8m@=YCOm=Dss(DH+ZRcpm>K0Z6&Os zc<0P!E;YqkPeH|l#(zV-36)B7O?M%$95&uD;hSne4bn)N7C6ZE%fH6Y--g5NMB?yH#3Ms*W_-3jXn{jjKORxd&0}!h6PkN?`i{osn3&^&*gQ- z5N3Zzlw_=ua`uH1d4X@u2+4d3Guwc)D>xkfidz?-5hDn;QP=8t_f^RxBLxdz%l~PI z;Hx*;FW2Ze5)53CBMoG_Tdy{FVT9Ik-VwnSS?Qf?a zw#m%m%$0SJw9KJ==}C+c!LyWD`Qa_F_~2E8zk1|nsMRF;#e_t3^J}3s--dG}sLXtg zL5I7G63pEqx(LJFo85vunQZ|x5H@hIJ@tqAo-};luT#!F&DXR|zn9&+UH9cuqeJ@+ zOOuT~^JSlg1N8;nsc5ujJ7K#p*4(So2XXK$i6})vI(D&;5jP zNfljnh#7gZ2{L}7?H!^MyNVGaKP=8COhGBr`Ov`5^2guSDNQ0u{(Mlk=z6@lD3RXO zu@J&UxzZ*&ytlk`S#IvXm2&;|%T1si2A z4QP@8FT?L%7>$_)<_W#xp#i@4D*TE5F;!b$mq!1KdUa*zh+>yFA?WeS4dVt(U}T9G zohG+j7GE?suT`{XbT2=W?EN`EG_*whNV{y!OW_TPZa(k3Yu|@CY9JPPRr}j(Q-q*X z=yp}e@7){V<#6BG^!*3Xl3jHh38z4mCica@Za}7HNHB#WV#l=EcD*UW`pp+b=b;DS zNJsbFE3L2{uQEQz_CEOQG812{<4iT{+os0X`E~9WdRmgdJM{u)nq9icw+-j+fmk^| zbii$*FP_B&Jj|3lYUlobKk1rl$-H-6AT04X5x>aEyX3hR@>})PQCg5GYjQz{XZ|4z zr!@!NXDA(R@6TmZrJh#uns?7T8N=0fI!D!ryB_tmynXN_NXW^SoAjyBJM|;zLYw>u`>SiSM@2C&=h~SJVw1Wp^XREv@uI130Tms3TSrqqa3!UO_PRr{xXN?pNil|l}s(i>7iIjp>DSeJmZ*pRg07PmwE zTp*@ntS)4%t>m4suRDSq?kj+xCLbjN*>`Nm`$?k&({Mm(l?Te;OnASF+AHlVt+hwgsj6&Sj2s9p zy_3{ zBd5EpJglgQbjH{z)KYzyh(uto2cERe39vATyh7!kFlb{&*Ic+=zo=!*4EbJG7SD~> zANi{Ty~Ag(mjnhGd8}Ph)6V>U{7iH?fmcJ{`YaY9faP^PySR{i^5&D_%Q?Aaq1vy3 z77KD&{K5lV(jArgf-J|tB1}vB`I)M8>GO5HQ+1in5vlaMC3!&P9N|BbP5-}loT5SG zib|*i%#%6D03F4G^02IfKx@?j8(JMX_FUv)*c6en=b=60)#g( zU**70(OmP#yv!`XjLjtT+X93WLq*P#Fi^5jV6TO~R>0Y@&dI~~tu(uC$YYAXU0xK| z88ZEYdOVM|l4-{7E81MaiD|AP-5FuAKPpn)u&)ju^e%bXaOK8ExMB5h`q9V6K6iKh z9a!##&V%|X+9Z%!xM4I=;Qey-qi0~*?fFiKscoAKNeK6w+SWGVqJGl8-jdxQWv+Z# zP1e%#ZkaM#Yz;B)EX7KK#lvhhXwtZmYgKfZAcwsoJAJ9vj7E>1%&r~xP%FRYTc1rh z-|E%O7kQ$Vt|<<9CW6|kHW0aW{IYw;(+L@ZYYQRVY?@DUSC+LnBUh8yG~rMuiTuQk zewR5)FND$F>^VG>SKP+OGeX(C88UKnl!rV{qR%;almNQN#WeDi+2dlV_P(&l$k z$8cCNwb_)vu9!~^7658p6Mc&{{)mStsqg?sO3y0ka#*%FW{eV`=S%>e8&$M z2<_lH{%3xf!Df2?MRT^c^4{xa`?++p>&mq?svF?8+i!%F<(^qWHD!J#_R9$ry!x}VegnS&oS{Xz zA-%kb;&p=rSw_0i-|MfxGb$yR2yUL~YxCb~N4zpWg(8>temAbYQ__9}JLU#@9>z6adQezeX!j_Y3Z zyK18VJLY$tKzVNkd?a&YmxQ%6cR|EB2a8w$ zL>F`o7FtPtcUd}fCfQ*c-kZ4P{i$8}cCDHRTA!>_*+N30c=t6vwJZU&q0z9K^B>7X z?ORZ3#vm}W&F5%VkQqdhn_i1rx^1atg>AI{?9(ByFH%tLYkh~n)Mwp>v?@1rU|Q@V`0Y&LBoGcb6LhSRf(@RRC3p0(EHBms7FRKb zZKs^MwTb*VoHu%7G4kS2rinUcpW5#-!#}^XA_e=8OBo{P@~nxHCp1(^0ySo$?|jOY zaK%CMt5J;Yt)mJEE?P0gtjEte?|=VzwlMG}Xes^DwdMYSPAN^;*HK-)*dX<3$XIdv znG9Yeu5yYtr6Ch-{@sxB?Dgrw=kny*_%d~ag_LNgAqksiRP;DhkPv|8+u;= z1$x+{m+jrBBBqu^8woQT-m+9V6rO@WVM}{(2Jgol|}oh4^O< zlncoN3!+zZ&U|F6+3TyrL9?3h$|X==>II^D(s+KKjTGzT$jwuE+!jH{LR`8#){-bW_+p_Mkmy99$6$!cbA@)X`^~< zlCLOXM>ENL7HTPrFN2a5Xh2sx3BMLrijY5Cw9z>^Nb&0H?u7m&p1J=>`po4eo^cQW zX(rPbQhxac92kn2D-CE+tApZ|T*s%bfQhf%rVi!sFChR&h?LFT9M~k+rb6(r@xv!0 z;Fb3SNrFSmoAps*m9XGLxPO4wTa#O(kpt4MP!~9dXtue2xV8Vv$-M8ztRZA$be?EE zU+oSJ{>VIGD~(!Ia;a6fO2ypPnrOYjCpBwkyjk9TIDS{}tKnV~-eD_k1M!QPx{VV- z5jQaDMrXip*nxzS^xkFIDSKro-x#WN%S?v{C~g8_bx!>12YZ!}^j);(aumntvlhE^ zxQvB>-(h&d92W~f>D$}$54`SsLC7|FA~bZ;pcI&hiUyk-^NZhXb*g-ZxD}hhXUjIR z@i1fsaRLmldnO9z<`6HJ66 zO%^EsBVhy?lYJ54iQ*r8w8~8SfGHTk3+G;j4_ag?vAmq);67D`>Rw4iwpGU?j^{nr zU$*rC=kvR-)S1!3-Q#NA!c|(zk@*AZLQLZs$NmEz=P;W38A~pegRgl*sJydpN7kaY z;@a6hMCl3WJ8`SPwR6(;DNv_brNnK~?6NbM=0vZYgI@7g6qS1Q+!o6nzxu2Lh$Qjo z7Ght5jiw3mIM3-g_>uwsp^_$`Gihs zwCYreRwKlrR_%zSY!i@}AaNr*4mC!sH#I>7{ni8SVZ7GaJ4pLZwF_)yyhyiIy^i-x z2vq5H7-wg;m^0a71a`X|92&$Ay)Pk9;S|*P<3$1Nn>@6Hk`QLL8kHi}%wA}vDG3=e zCy)v9UC(Y3_^01jOyw^C^6)C3Cu)J>-v2I8>TfAr%0vo&#HZAskc@8GP{+=B#M-wj z;d*6o2gR0cA*>@|7kj{j9fsWq0Y(4$P>%FA{t{2L*2L3IWFr`V?8t!ab| z0;|vMn9yPiix_K!#HIB|_9N?+C+aWaV>%S=ejIGV9vlkqlRxrHxGu4Y)KZzhJ+{Lm zAF-7JIa&pu!$C?2{C#9w zqA(Q2rqL$yWUphrE!!Z0>pli{BGO%n;oFc;3d!@3@8-Quxh{UxZofG|3||C7_NSL`8InKqn4-35*lJsu~US?S8 z0C_g!r5q3?D$#s$ZSPz?M5ecsZVKg4Pc=w!%6la8Z~TD`c%I|T&e{DjpF6T@guEr} zKa$wss7RmDQ4hYHU7G3^fSy!#U)ifnZjzqT_Wz1}jh7R#wzSPXyCnU{Q=ZrvdUnF)<{= ztu@$=Pk8HvXa3%gwj}T+DWxD+DEI5pqq#Fqxg+Xk2mL}n#MQ$sSnkzJ_a?Q1@F+yX zp3U#nkYK(zVjS!>-Cy)+&Zj{dLfTAx%U2Y0$1Lal^Rjy)yYJ-LF@~|BxnlELObFc5 zsd*fOvJ(vh%f3?C(JV@`^Bub*$L3qMH3)xahTW~^bVk->IY4LIKn`zCqASiDZGek# z)&%sG={Als%6oq&W#vD=D${ReuInB3&+Uv(c=C_(|J6@CPS8q<(dw{!%HfbJ3m0-p z`6%kIvv1Jy`ZuLST^GK9Wzh#HtEmz3?K(Q0g*y3tBB_-+k20Toh7WQp&GXghrD?tJ zq^L^kQyvY**5w8fgpJ`{4lU0ZZrv7Wc*viD*^ElrN_pv{5QaG_JlVVP|0~Vl{%?{% zvLc^|@{2K=oJ&_}ar;VQq^mp&Jy}E+B@skHe?Th8?VOvOUt(p);12`Er?OZ#A91I_ zN^#!0I2~HP*o|xolCvA(!(Ag^UuEb;+il@A5+;YaUV+;-v3(=T8w(}l8f@kSt*rI2 z)O$Y5=^Q9j4)G84V8)Bl@D>sF@c-X!Y2SktNRzB}#$;?})z4NMS%~N>Z$R6<} zvhoXmP@qeHXUeHVnzd6*^vg+S(@iqoswv$Iu@b|Nr2G*U>nSLuik3Gw9Ir5==(DuA zt$yM_>zVA1h-ddM_7HqsCPKCdTAJUvp2_(nkru&_i?p|$gRcK_uE76<3@rR(E9!_4 zC&_f3`eJdq`V^?CS8VB>EEtp|ut7wc{@J^LC$u>(+jAqubA!AnrBa?bmTw#f!%KPE zrp105u|Mm0%5EvGX#i{ihCWBCOVCxRSc=zq)qib{&12bd$Ng)M7(K8#g8%*IIir&= zb@Yfn8Mt@;XN&Hu-n`T23YK%=V327GbHB@WJaxJE1`HR{;y&R%)T_cxjsvkJm)M6* z-gQlM&e&QUi`tg~+CV#~pl+e+41`HyPe`u%GT33P8S2DWM-@U_XH~+2AlU#3w?N}s zeK7qDwdspLYMF$hjshmyRI8VLEKkJSyw;EJ4GJXZ{jwFCE*(Wb7nV1T?A!IYgwmdH z+qFAXUI$Mejp0_4Wmw<(omc32(cy_Mkq&Ku!x>fs^a5v&@uSNnCgvu**(5D@XC6%2 z4e|SGbc&5i_5=2lcf)Ar4{pHQ0?wIINpUn6LCAdHRq3xA+k3-MUv8q!2F3InGM!tM z8`r9ev9s7vPBaqeX+5kd-Fz7nufhP!;P+zXURlndg;RfvFXK{2Uw|19OywT)Qy7EuaP*4|2Dd9(?}S51zLBlP zy0T+^k&~{=E_?-83_18IH;$WT2di0NaP#V~bc&NW2`Xijw~bGN*t(Z-*T4{?WYErA z8!cg!I()WYhMn)l?il>RmLhC&J6qj+*k5zzrUXf468Ryx%`llO^`glH+_QL+nU2@k z=a<6Sj`}UxD%?8z5WWxndN#oG`B>`(gd5r$VPbT; zG=B>NAH+q%^LOz5=}flO;ANjwg>>>kq742&l3T$3LQtK-G;e0drtOPWSB}j1t4IWz z#t&uZMOmqxtbrhbXcF_=U82a&g#*~{3WQM?$=wv)a*Rj<5f5eV(8-nY@5U1DU)(3g z%gUY90bO=d3IS$|S#?S8*ND|Gwc z6}|n};-qi!wSH07fVvmTMKFMq&n=vB%hdLaIK5=@HV=oS2$|t0So8c^T_HFID~!S8 z7|<~tIpQ^61U&S5l5o3M{~(|_Ygcmq+uBz>5ahSP)DQd{m%m`cek>9`U2t0QY{Z2Q z@mDMy-Xha`Sz#l)Ak()$7BM$H^tR{mNNIK&RfHmZBTU8M5CydwyMDt_SaHY_6$M-Z zpcp)UA8^U2KdNm};9j^F)?0ei|Q-3f2Pp}tiU&W zH^AwiHn)s2`%L`$*XRKh7#LH16?Hd@s=D%fwd`M6uBXicYl)iA*``EBzr9}OxInD@ zv%I00YglS}`Y#l7yDc3(hDTycITKjO%H6AYd+Y6L_;5+41>kJ+0S8&2OeH(rkA`da zZY^*2i9dH2d#udNJL^bps0e9@_(GJhe_l(q+9Xo^6F%<`zW~TzlH3PK+|!T7-#o25 zS>ocX@zfmu)u+*Z%O3*7P*UK0h?6JLpix#!>&C+bwZi!fRma~e%IBBxSr{Ye2Uu*4 z+QaTSw#oW-g);2oOZ@`v%7*KDOK!pAxA|0D1_qTbG3jTz)dzZG*QAEC+h`Zqh0g=g zS6-W$DBpQO?!Y5dJNH22qK>uqP_#_WT>w648^Th@EZ^_LrO~@i3o#(t9b4|QK7?!$ z#65N@;mbxz0KN(M--d~TrMnlUt}Y9=)JigSw0m5&6c%I*#936XCBX*>HY?IBfW>z4>iH#Z#Y&d7oa>~$BTmqzdEG)Ge z@WFfzY@GbdG?hb2J;KwBiKDVTSHG#G*8bU#HnB^fvR@t76sv(3yv+pm&*s}K+*bSl z#LX0OzrLN9!Cc>^jkI_dP&vXBu?>5tGwB&T+WerC{I@KK*%p648||WCNX~R26E@V@ z$t!*W<|-Q^Z+sXnpLsSG=1|Ks)nGA)E5VhFwl43yuZ_PGHTL7D<4Tnrl+kK8*dv7R zi^2U~VrmeLuiD>V)s~uc6x@Cim%Xz4TAj(B~r%7Rca=GY0L_ z%lcwAo|Y`Fitg4kWo<3?_yH)z4bjeSM|%>S&=|PRuFbc`@90hf$i+@yk7J)gRtd}) z^ER!q+GRV>-fkaJr!ZU}Q)C+O>aJ)$wL0H{b?54e(lxx@1dm&0x#cG^qb`*O4540WZQ4lVe_> zgTFE7=i3#f&n-AQJi8*fHK|5haSmS#1UpX^OvkL!ij(_VJ@YHC{ zS*-nD@(hABd>+zCMiw0V$omb@1NRVffP{To;rL{Fvbo|=Ny?k!M9xB7DI{(r)S>g& zMq?q8u9P$ds}9p*8S>@_XZ`H7jfv9D!6RW1Q(S2(y49r%wDTFw5o#R#YElZ>HwByZ zCl-m`m#X=%TRrNVyGdPHuJuhIoF!(I%`xR0Os9geV6Amv+=>|B9)X|Fa;0-F&Xch1 zY}bxr5t}VOhyNNWWqoUiw5*8frBSA}!V_}Df4KIxJOowy+WW1{DCB&xvN->6{O~Bp zLsO*yu9kq=vA1!|8)YhHnp-@b7Rt$*Ble1>9Kpp$EB6h1_iqnm?_dIYp*PcgJpl{p z`&h+Y$$NV_E4dSY!|B9E$AI&K*2Fc}_e)=NQPeTSQdR2j^J2>~nOY(C!Aym!&~J+i ztyB0c+#8>K%*4c=1;01;S@GyVm>2iZ|L`S&Ekts{8x^c@($66w zDo$SU3WZ9z$ww-D;6FM3Ajb6ZKa!UzhS8GqNgwuNrV^7yFdm3kv{O|ehy1Dvtjk@K zBv1B2hU;Kr<@TqQqNs}-BOC*MUS8$;78Cn(!A^CxORndul+_o&vPD`j*8I%Q$xrxS z%r-^91^nncf#w`z@3#(!29iSEsFfAIx-BFfrGPhS*^&`0b-AZvmr1lsvX7);9W*qf1uZ2PH#3423Gm@JMVypI# zLeBThf1PeUy%QZ8e zb$$Eh5?<4v9~f#;v>q6 z`=w9RLf}nXkDBrTlS`mT-`juLC;lL&^~*{!iWb`uMi*`c_nW56mnX~VEoj-=LaxO? z+wb3SZ+(5OFr;qdghFg2;1!4+K`JPjlO0*u_P%s=I4ITi zbHog(+rZ5UzzR2^S6r&f>SIqcVf+L&MK^Us&V&LLCMHXKMBc+^&S8N0N4AF;$3=YF-G;sRh$XK#K@qGCVV!Fre2JrH4j^l?-vql?az#ds}2 zw0(vEwVK7}G9T{JsLYZTG%-_olv>gX&2pDQp8qgyS&v|*)%o?i>>v92+s-LG2W(H9 zA@ogAWF%m6^#q=GWFFC~G1^uL8ch_Xy&0)T0ZGuh=>j$vf5#I~j(TWg+8(O&;+QZW zf8#7MZs3LBnu7jJU{d8{O^1|wJZVi>^UE$Q3$#P8l?_5nxT7j~-?#?>O!5L2*!b#h zPW{1y4g+*4Y0#L@M3xw1?}fu-CDNaJFnz?eS$h1sSV&jckh;Lh@>EuEq33+U(Q$k%6ryRGOA`t#lp(RO*Q9$nfbF4;X3MsSLPFUSAzK<^d6y z#=R*WUWYzd{@!#wcN}n~_4WaZg@u&TgfGKAR2YOsg3@nOOs(L=o9<*)`P!ZDhrv$f9g`@U$G$cy+t!oO5*2 zx*X!o9^s{5BTcUVL`T71->B-wE%PnDgIz#cggw%Sa(FF**PuM}$h#I+&BRLN&D_Qk z*SbJz@;47Hu?Df+0dbvJHto5S-%&nZ#g?w!PpUpN4f4nPR}kYGory}7?*B;WY?d*> zD92VyWPOlDc>=owcT;^}Pf!+)`O=OpePTqR_ord+-#Jiz-%?Q#|Jw+W&Sq<|*aSw>9@@zWEFMGOd`d(GH7o*$RRW>z`u#Z%}1OlKsbBob3xY-@>;-=+-dY$P%~ zBGCy){i~k#4W1;58#l9Gz;EjXJLvTuw3GV)bP)`1mYt1irbh?b=;g7gJe%iukIYh3 z(4eh!t>Qm6%-^i4xwwv(jAk!miOMDlnT13;<7X>2d3eC6`w`9u&Cs&~s2N9r%|kB# z;M9r`g>DC1JrC1d7{BPI5cbT2XVXTumsaH!-u}<<>Hox`{olu%H+7Qhk%0e*V}t)i zikX}UvenaUdARVpJH3i5kp4vvi|7ZVNRbg%oi~SmmR}9Y;+0rAr4ZM0cK?eG{Xdjw z(!!ItA9?$&ymjU1$NBhFCv5Xzn?TX&Wu6GO`Z`&g67g#1=Rcc!XDwlVurs`SAtTAD zzv9{(BJrl0B#qzV?4c^{*+OZ7VNLcI*(h7T_Z@@&tX;D zD#&TfvMv+-DLfrC@G5vs(v+qtEC7`-p!dnA@}+m}{$u)M=~I?Gjz@P;?VBgR8V0iX zk~KlN5_jHk?&<$X4yo5;bJv_}a|FzMJD5OsBmm*ug%LfBYjG)t^n_CUiXwbq74vxZ z<0ELCzZIj7nZU?>L;$cb%XB*XQb)FeMTeZ^RD0A4izB)v17RO~p-evtX1()xlZ_oX zA+Of3rkdF=*@f6E)laiGa3ZS9p^Ag{=2g&;g?t4aDj(5M6CXQ(@M?CC{tXv3T5gz- z5a}FPBV}2@2BvR6_2XJRhdaH?C#P`b2OjyGBu|jGH#e{-vb#6~U6ZPK#v?oY^l?>2s%4-8D*GKXNt$D@+<}47UGXkTtKqs~52Fn<)2Q zB`N^NW@pvqF|Jv^Y}A^c544N$ZlomNThJu;ehIEA!$??76n%MYkV5h(1ds-O>D0<+ z)ZHmrDiMZQS1P(~mZu=B!co|oC!ZTE?zQT}a>qPx4?r*O^djN(l_Lx|(fko5{=%u+ zTLtwfJAy2jn7t@oxc>U-y9St7BNx1*@0@AJ?{R3Hp}+C2TdH%~)K`O|=0}wW{+~Xe zw=Q>>P_<^`qQ)`pNuit-tGR{#jkNKl=OxbC=9Ja6h1Rojl&)fs(c6_S(NE!s6^R8C zf2{*hm~^bT<^7Uof0160@GZ~e<#I?Gni~~E72u}MJ!|k-Nu2j{^78H3Svrr^#Rr%K zRPEQvoMjW*%Twqp^O0O`*_v7P2m5#Xe7?-jtSMkQNP+gRAsd?_r!!g|p6##r`L=Ab zCG2~A7QNEUn3n6e^oJz)oqRQEuX_Ug1{=AgMpX&CwWT3ar+mT?GYVy|eq!$;1^n_W z0tS%IFCE9)BK6v6!KTEMY(YFR$vA73{P})C^}!S=Bdhx*Dvf zSmpL~^K=k0Mx>0LYuV%SA>qD?Tw8J=@B{QtO?ds`&l%AUdtM2h=i&;`yCbc55%k2! zhz})Z()1*~7@MKPAuYsd9!$cwmxW&h^jQ6-WmvYjrmD=T&tjg~nO*C{=%M9oBmkstvf${0Jz&BS$F&`=!BX}=!G6`~piCe6L#9Tcgo8-( z-IdhUthiWB>bDElM&8>vilvu?e)bJhP(a(Eyme*`iDOJs6LWk z?eVyjs4#CT^;9s}a&%0s+sk$D;{v9)C;Q=)RG@2u33czrR_C7!^{&S#-k9EJ{gp}) z9DAJnZQ~D|+4!cHnYNEGdFdBx_rhPfwMwqE%i7YqWGJ(rA}=hAB7swX)iO%;-}R>} zM8wJlkt}J*-ftJZH^+Yt9hFl?OO1k0n@soAZJz9zl-+WO44dtDsH>RgOqkF~ zL?^Y*m&H7-_4uJH$ld7=r3r&DC8>r!N-iRuXriU;ME!|jOHJOWV>z(9BHKd4GoKnb zJE}Tv{+_=H!1C<2+Rh3&-{l-`0J08 z)ZzOi0TZL~-X??PBz@H1-T}ms6HytA^#&(p4!uG=FF$?|ZD!`|=;sJOyEa>ppNu7T z!WH`9T3AY3m4eyR3Nt_kXNms(?~fF8JIu{&C>q$gm^?t!jCpyJUB2IgXWj=0o?IN; zEH{6uyIVsyN%F4I!Jpl4`R!V^S;DfWuT<7HZ@@NZ*PfLtE^GHPGRSwNpBg$&1Js_kw-n!+rf5)Cp>~N4xk|-(e4D^p4pk6(A;K%FW~WF!z6B=uKyF`frS9*}n`) zaXfO0H5&U|f1O?fk%5fcv0yA+9_RnH3ne;fKPSS=S((t4!xh&nV$MqZN(G}7wGY2x zJ-t8g!hY4jKeRr;cp%%<*R;OqXpSovnPN32neB@huO+{cd zaK?rcS*F(x0&O=eUe3*n@k~56DaRy&@n6*zZ0YvY&|3WWR~J&7zvhTmHB zao;(_sX6``5ObP2e5>{}MZkuj;xQ!)eQyA-T$l&tKZ}iQUln-~%JuIj#r3Ng)$XmD z89p*~OV==#1iE4_T_Py_5_;HywFZ4hwtXnANn%6g)4kPW*6i(g8@y(Ip@db1jOEZ^ zwvZoc)C4!{?}2x-af`iA0fEqv1j0*IYAhuVf)*`_SQ|FbnWC;Aa$qmu={szI=czM; zW;bAm#TzQGwR)5Tvz}KG;U>VxnjCK>``+OUi1C+oem_rw1XZWD8qm@(H#c3bz(HMNN+^jdLLWoot2 zT9eo2FVU$;K)kxF>;2!r`ig1MRuAhf_8uU#k_Xji0`Z(GNguNv>Kv&_aC z&_r$SsN+$t3xej^?oD!ZrE6GWaUAybkJ87+khP~{FyqyMMjZPPzSfk@c-Nyl6V$~v zPkXFlQW2WlgWD&;9FG?27G7?*0aZ=hyK(QT9XhOrBa`L-Be^KiQ8q7pZ(i&aIt^?% zlU*lyYxYmuuR8eOAHo$^nwyW8*VephMK9uKFbRR_vOzUX!Hp}(&DN4lLy-{{=^t+B ztd$h}If9lP@aj#bQzTI(;n4T!uS6iGc_IWdl*5!h7g`9}IX1x)U(o}s5&6PdHI9-AR}MT@TIX1E2KU zZ8AlgkNzVGVZ#4^Y`u3pTYntyPqnmW6-BMKs8;N~Q&kjIRV8-Xs#UdDtfXqs6h&*R zRW)L7VyC4>ZDPl)m6$=Kzw^Dn`+MAbANTji`THbCKF)i*UeA}#;sk>MkI3V?Xz-Un z#GnaZLuswpP-zulj1TiPF&+&Qpk>v6neOE`12)F|rh>uFTc z>LwSq-GZgY4{g1H_xlGTM~L!*rXooUA5)(!>?y`Qk}eB;qrNIOr#7d46$`nII5wV` z?*GDnpdg~;g`qk?f$=i+5dXUBa`aq-JBJW<&*rfFY?{I~sZaulA#RrKJvXm)Le&<{ z!c!v)?5~zb?#d)@-QECwW2BaHZ_d{3`ZM-}Vln6SCge!fJV;u*x#8*c(rhv5g9n1o zXh^hZ#otB6e_uoP)U=YMs%+d;$wt<`%X*4MSBwq#(QJp%o&ic3#C6A8@!2jm6$IX7 z*YDWCh?*x!VybixY<8^8d|ggHRcTS|L%=E(Q*SkMY)GWhj}^eKJ$W%O65y= zsMEE|zQsTiq=cBNh2=H$Kun`#Q+f^eCHP09&*+h=g(F2qx%*$UAcAT(l2*YTy#dC_ z9$oLhUOwqnFR9pkS49|`v?w6jndRS@dqAr&2pYlI!~D$;xDP$jl8Rl7sC567yJio1 zpRxlY*qItDwTX%QdG*NOnLn9tbI#7yAzjGhqTz{eK#mUi{o+>}1RA$5#V-`9mh5DQ zL1|MvO!}AkSRN-jv8?3$1YOGPBEk(5FgPf?5j%v-Xr5?*YJbHaVV>N^MZ65VHSH}L zSUOYolj}=hCmXjjW{#UXuy$kMt<0}U!%wAh_z=N<#<)$SwbB{w2E3c20^2a~(V%Fg z%?0pE^4qw;`9O)sRj!T!tcY~`^DfyV4sQC}jZZ6K>B`*Mk+tVLP-xJLy?*RHE#|sd zsxccZTc4$+c2H^5OFl6fN_Bw~7foBgM5%8dSQ)cA({rP;ma;TK;h3k4puT0{mWt<5 zrfi7FUH(^jut8i$8AFwdEn6L~)yR>04#SNrusn6)Zbyr~^gLW^dzPkp?Y*9>pL>nK zS6n=z0Je_N+p8vuF1?Yzx#Ig5H2L-NX6q9!1Y@@D>Ev-tYqkDOE+=1spIsu;(0>%x z!jj08FBM0!+UwS(rx|5$O;&d@XX{4Z#W0^Mc*p+CTYZQuRM% zJy&83gmxq^a#(n^c3RyR*aXNBHLXW0IE77Y5yHGk^FLtj|8*h$0V9q7lkW@i?2DJiU;j;bDAhX#1OW-s3(WktBFqW;XD;PAAOyJF zNnjN+2)gH}_DB)B_ZmrrZ0A8QQ9Pxfr0^GiNA^cArq2r|_V<9N4F@YC0#n=+IaH{i zt?s|L%eV7RZ1`2+Ur^}&AD;Ow$OO#j$pEr0-w-JxgKoC0B!B*8Zf0yIxIgs%u;;DJ z@0)Z^D!VBvjEK-%MJmIbv9}ZZKNuAF^m``CxK^q))Bh?u-Md?-{W_ZHi9>{qEYF+N z(OZPShhR@8P8Qyt7Mwn5=OC5z1iCM+oijV-ga6_e%wguET@%I-Jve1#*Q0Wtk<{E% z*PA2o@k~td3mW(WK-#b*cP`XLL?d1!ktR^~!w$jS5XGBKgV|id*UFwfO8Nk|eFJ|F zq*tnpfUCW!>+5(;?ZCeAwVgU{^$Dd$vDBNv$i^KKRF_=QUpXcnTl}JUiX70h_`?>^ zk~=>ANAYy%TA(&Qq6d7%aeMi9!Ma`W&;dNrTJvf z{J+QexoNB3KSRz6zddrfOWa^2g<}-1i+uF}cwfr6P9u5^;e$}la&yqM*`(BArJeL14zzU_G6&Am0NsW3p2Gh!95O{o9soH@1lYULR9 z(+b+*?^9`ijD;@HENiR0xA{Z4&16pA_O|#qth27YrXlQ{C~}Dw;pupp&jo~EKkfp` zY1g6eLCwqB+dre2W`J4scbMOYX)s-q!&?uka)~~G!EA_KbDN41HF(y>NOMpm)LC-h z)?vY(?o-UrNRt&;7h&dcN`*${a}Oq)L)4=<5$Xc5xMSC<1&e0Cx)a;HWLh`llfZuz z_irQA#=nZE$?PdIzZCQHp^Ha^ieCxhn0))Ak$8@|clUR_;szqH3Gq^4fCrt~3318EvRrI+BN$(QD({CjA zUZNNC&6MCE3$^Z`GD!pAr~D$dIa;PyQl6Rq9iq-Qtr z5bV~Um3jLL7-j&STZd)MeX>Fz1=0Q7l4xX*{xflO`y`bjFdIGd=Pj8Fv1ok0P@f`n z&l1fl3*}#NJpZz4(NwvYjdzXcxhjHoK(W9FC##jFVHiEJxIGshH}JxZSVS{a8$J!D z@do{)h_HS@1pS!CaFcI+na1kz;yh(kWO!{{-tzZ%;CP%)7cS!36lO{67da|4gtVT% z^7QYR<5-I57Y1_@{0*1THQCtg_QV;IR^6oCXDf#+g*vkxI_+CmckhPXef@cL)Zs?p zeC?jwv6wjsuhwCATD-sHu(1FR`X7uHE=$k6) z2>k9^zySSRlj9^iKZ~A=WVM?r^?Y)tZu4jN_REyypDHGAOMSTGGgU!2>Eg+-Tc0iW z*hec5{hXDqmknM0`zONl`U~B|9uJS6npcxnYK+D~psZ%AP@6&!zI08cN)1O_UhcX7 z>OPybMucphn)3MFAD+Q8Z8u6w(^Z9?zS6~h>>_IV7j0ujQMK1ov!d=i2OV-fa zro$Z^z5CzVvWW}@HEklnb8ZQY+C_j%!iA!%i-~q>{U3!GaCrEK1}2rBgRRJ1+P1kw z>q$NM-C%i{tS)zOL13I~F_5>uyrb8h6ni@PI1#)a;{nz^v$tqMc3D%5%dTg?**u27 zbga2PGWhU<71DQ$HDLRjY51jBQm*=`8{?u{2IRoRkk1v_{ieD4sqo25Wu93nWi^3c zD)v*ry`P$Ve@_9Ty>>%Wh)_ap^hsL->dFwe=4JS6q$V_5oEsv|oh@%Z6-Rzf!}j=A z<*(6%o_CK(I6~>wRSah_E}go`@%Piex8aiJl!WNjoa8TiD(kiHA1{b0o|$YcXNbJV zh+UN}X2YmVDz8&ezgvNDlYq^Ct+6tTON80wsZQP_$D{j;3~DL4_k6z%zRW_{Z`}eBp1Y*EDIlnhvs>wSg!n z#+AnUTXuAye*S)glHDdUjlhOc7jR2vf|u&#ap*^0r!tTB(k>q zgnne87lzt-uTlkO#2fbSqY*bJTh->ZI4tX6jpy%x6R1|;t2zrbpT3L+wm-C!_L+{0 z7sM;R^C>Ih;w7Pj(}Rd8j7gRd8>lYGS?r1Cqc3HSK>yGd zz9>K96@VkDOH429AqX3}E?ubqh9rrL-uOoW<6G+JF}=tQ6vcO`6Z3p+@JVK5>@Ys= zGEsT(rORAakfLnru?x@ZpdWBT%{vQ?-nZ3{O1N<6vt#a?c>dEcsw=*-caos(U4gdD+=c;=+6_?gzFHM^yG?I>Aj@om>0Gd+*3J`7WTfj<-40H4` zw9-Z&@2kw$v9obo-wLcUQFK2XH-`^uZ;J=R?*a{;BJt0?C{M4+y60^-HBTQ8JI}3A z#w(~m4&(5i%O??PVnkgY*RIK{^pzi9W)DlFF`t1iJ#Y2&RQ$*-~wax;Q6$Xl%Et)R1us0n;99itmFk z=;C)X8x~>%L{W>VA6H%&;A3%)&ao_fk(j7U0egcGm8?a>GBzH^*|Fvz#i$@mL zE4Q7^%;Na)Jbg4v&@XOt*WMh^{H8Ise06>u1~~8+GtFDlz7req-p{6Lt!^j z={Mu4DrmB`1W4#VTkQI8f+E&kQc1}2MG~Z{m;Z!|d~J2PE~2|E|5}vUbJw|1;j5Gg z@jmo`0hu-#p9Kv@;0;l6P+B%g*Fs{}-+&j?c7fkQbQofnU)+x|Acib$DSt_Iolohz|Ltov8R%eMIGy%y z5YqdemEW*p8iCD3h?>6KMXtp=()35ybH$z+%SE!X!wq+TkpxK=*`P?mUES7eoMe$q;hV7Z>N$QQe zd3e4;mC~WGXe`dqn(u!Rn!1l+Xw3Aj`D=ZDlK8bk8(`Jal0US`!v_~wfo&(00T%Gm z`KH0P>y4Syx)c1vdtt}7nfUsO`upVY*UrD8%YnJd$=!j9gLoTA3c}X!5PX|J741&s zHTd4SRk9Dx*rQcf%M9>bYVK9e>4@c(j9o_pc{=`#;Lge;b^9O!$Y>nL#oy1S0LZ;A z66$Jq-dyZ*9M1+xfQC9&D?5$L9%ZynHS?n+x_>0(uM%|lA)V%`Ly;|xMKCx8N6Uxc z(P98_1ZTUThNV7FT{39Tb*t!c6)X!UutM)j<9+>iPun(8zxbxI+chu?H0aU8S--4>_oc^?-M$Bv%&J*_;OfW|GKi$};X(E!?5tMJBb9z@HJ z1x;%7&GzwQQ>EG>v^e=Q!szt_E#o^()j{B$F*AHzh#Aadj=3$Ma;@UlXcGW4cpMi`$^f z-G0?Rr$E@J!qR^4o=GQy)`;P83-Sp&w&P%*Kv!?a zyWRbiCPMLA5=?BEd6<}(UiLvu*xVuE=FEeAxM1?ru@xxL<>xa!eUQ_+XeHX0?^=L?1C-g&Fi@Uk`lP z#)oIf_m#3l$=}|s`(P6ej2WwrQD_n+-oA5zcIWHuyXP+(IGUR8s9(N8-L614#!x#M z)}fU4G#yB|1n3dT*j2BVs zI7zzX*?oYAq7br_^>O4A@9`ot{i{Q|lk|hTZA|Gen;RPaWQ7ij61t@As|d(0wmxG2 zGIbfQ6>h-tE)Q6N(IYyeu-x(UH9 z#Q5Ta{YR@`&^|szRAr{V2`pY#unQR3SgqfNiW8v7332&y+iwVK*naP>oHNgXA!4$K z<yDv5YW6}U_fyFD$}dh)spMlS zS^VS6H`Y`#<{`(~T6%MJumc0i_akHd_RDavC z3fYRs8f(;o*}n8GHmf^>2&G{HPkFlS56c;clS{i3ikz>rUn75ODlpaT%iAb`hhzsK zKeIj=YO+%NuS-ojf@b6cbE>mk?JjEf-sqiCOQBSk-L9*-Rm4Gy;`0{!g17&losHNb zb}8PUY4ai7)w$c2_NpZ#B*1yoZ7n&9H5%oezt2o+9ci1bp!h^F3%lZvEAwzO(?k@> zPlfmmm*~hXH>*_Eh;>Z*qH5nH=2pQj(?6&AH}T&{Y?E|nagU97(0!B4hS&P)UHUgQ zpe0?I!+P0I?vBqi_v?1ym=Pjg^SNH7H(9gp`VS#(FRbquVV|0axVP`q5l873L|+oSUuCi zKf5_l?J`X@_w?rmK8pS|BI3$I^OpCOt;vf<@~$%EFtpy+KUBhiDfQB+6*Yx&lnN!Z z&$r##$}2mv&giFGX*=_%iq{@=j452U6>Ny#Nh$Jrsf)j(N%i6C2HbEHYMV$B8nW}c zz_r#jIGVl8rIy9+bK9K0thl;pYq8=LY$Mv}lW0nqngkWU^RUA3wD!SIcp&Hbue^FR zuCcW914Dw_*P1^MGL^$ncJ^t->ChPy0yZ6UgSkO?$9e8NzH4%(G3edymT_F)_a`(A zAz4Al*H)@2pHmaQiq&D&`!Z39PuMVXTmBl3`&|XSw(+V(>!;GqI<8i<7kJIz*NgIxzf> z3%xISm2qiAzGdx?0#zvx{RN@(F+E3gn4_0WTanxK=P&+VA*q7C$mlzd?S)lrSI#no zMOQmNTzO?LOIn&Aszqtrl+`kF8fnNaB(HvX5-p6=rHSkKmIhFFZpQf z)G~(8h*Hf2QCQp4QyC>_X0kl-_J+31VRWxY5S?x5CMsZ9hDIjr)^yJH!48?^Wy!I`aU|R}zxB&-96;9n0gj2p;(DBq7hUZmigAHC9J{l{ zp4jRId?*?H&2Trzc<5mOneF1v?7Zf2#8<8L^Rk~p_I2~wQL-!Z5mw9Zth4?w4V1=J~s#oP_9_G!~em%!gsVd@eUHq{DYbX7^ zF=}tj;t!e=^&a?l@}GJ7y2i14Gb;@UgUpKd%VR`Brwg=2`M>zwB(@;C_eq5hQZt2(W( zJF*boVv-$Q>$K_Buv6G-W|M&b!47E0$}7rNe(T*tj88Tiu$-^5M7OHG54<>kyzxzY zG!Swl>-ZppaAK^@rw&!^wbNMTEeTGT^|=7;fFqk8!IC=b(?p)KrSRV+WU#Gsob-a0 z&do3>JWW}&I4(1|<(pQAWoIjo1e0tT#bOS^AT9ek z*zTHT8*1sZMTtPK+G87;cWupeHXBZ-*!qN=dNX#xygLoYs^9`RptS1{Qd6BF-MB!> z=*xF|wid=LfATtyVyj!KtE}^ASU*XJd^Lbn0R`&c ziSN#&67#=I`Ik_I#QIBILO1bEo%c>aYxhVuBD&h{>Rg_`Pjm$JxL*>_l%jLYV6%lb zu0Q%QJGt)-%#+25%;4?Db4Mp7+k(7l$6LU>)?qq|;va>v0==8Z1$WX6+7i7uXE5X5 z#t9#9s^4BO>#N*=-f;H8uvB4aXR#nP*j%dnz70~hN#WP)oLMy`DRe)XgXqfEV8=PH zFDSaoPUK%f0$C;zzDW3;qZctTT$^W24>S}rw*eFX1`+G~KjgA0( zl~A#Lm9KpLLj3%d!0vUIpnCsL?;lL3^{Q9fA>887clp6&pVQk~vWiS;EB%Ac60fBu zz$X$XwcYOGIZ{{jh@(=)YpB&mH&lEOhDu+4s+^&igV1^vH1RpsBL3=*sjp6kyG3;< zpPZ*@{rx8tgsfzuU64k~X10Cm%Z=BTT9*S42MkTm657iYsi}XnVT~zh8~DRXpo48K zzSMc`=4Ux|4{Hd8YVQd<&>V0zb;6b2O0_(X=FmW9U}%dFCPH+g^pA&qUeR;g%K}m* zrHz3C7uq3ZXAHPy4l82{{ z*STX&brq4*G7K$)!U&8${2R9*VFu(e18@w1Hz>dKQ4s*MoWia=L!Oq-x$^59%t9cJGUF}1ZIE2;RWSg z`QI+SU&ZoVo7Yjf&zrH_aN*hgd~^8Ju`#29_J@=XhKlVFh(!;9B+jWXFYsd42JSJ` z=6O*D1N1(`-{b%zxPW_lHk4Bx#FGBQ1hbra+V%maU;ifU#6+JT@Dw0 zzl^yL>f|O@EZwtnwy%-BIA6@TTC~gnoU-DCM;}d{&e94e9iG;(jMb z`xT2shR1coCCCRp$>bd?_u5D-aU4e8>wh$=et^7vWu`&(V0KJeV%0zwzLfFuh9{=~ z!qNB-o6gn5e1rt>FH=}0!P4GW^6-#z?MJn9{((jYSo;*A2Ks26S)&vbg?;CrQ4i<( zBT8+ty=-^@zn4E8s5>L~?QI=h-HVq5q=Aj)+euH=f*&rOIqty8V_nslHv_5UtTWq9^$r zt0kFsLHcv#TBF79$(`^tjwKEa;-?SnA_g0N42VxA-6qKrwzGI!DZi{q-MZK7lK>q9 zTI6#>rYP&{26V)4>K7$9wts;UoO@Uf8hj7zAB7(HnuFb`rNVi5E)dcM$2t~oucqN_oapts^dc}R9 z^4KCLpQOeCHg_OzyX`U%aJ*b|V`?sN>3Ff{Hn+lN)8X04L7|&kWr>lxqt5`Lbo5ES^o`!W@zCD`ngb#ffPG z=P*UYdE*zB4>qI{t2TT`W@7oiUKabAkHd1jBar}m8XC?u^$tNtz6}2}@pIm`wP!y3 z?d2V2%Wswx+9y#@cTvM&q^rIC`NKI_{Syz-mWL_+p9Sc{b#`!Xy2w`=$g-Khdy$A& zBm%;v%hCj*gcW7_@IG2u<)Ik;?va+t@+6uapCCaVL!2!#@J9rra zPXgKXMR&z&1}Q%i2zhz^(9QF(SfD#xQC@5zmEvOItG2=US|sa7$x#|oGF7;12YZb56n@8%)w8ji$kC)q;sUm8U_Elz%RJ#HN?#(~W_SEVR$IN$d zoI0KGEsB%ai;kO!lILOr=l5XE>VHVCd4iQym2G3TrsZY1flF>JZNERsLzZ@03ix0< zy)*`x5Yi7Ay_=+D*t4bQ{si*EA!}9u+7FKjX2;`oJO{YD(8RfxaPND z-}j+8Xq;+~GP=MQG&5B;-n6u$_PiG!@twawt4g?P=zX&f)90kd1FUEQsic|cy4a73 zTwtj|UokE5Q*)RP_BUy@?dxyZM) z6${xybY}*1f-ki(^atc#7Pw;OoDlsbzny|5Y;b#>Lzc+%+FLBw+k08L?toz|Y?aLO z{TkWt0(636sdVpL{qjDMCkY;L!1s};v!*yN9=Qb+mAUTQ7Txor75LnlDivV3M3>aL zTp`$^1f+okT)cZjfAew=i}+bfLJj=Gc;>CYGuTJtB-di~wfzM1Pgscyrj-IG`OZIO zfi_VyX#}l18~7?lVh0ah{e$I>3f9vE64r6gYjz2^W=j=out~ zm?rh-y=P}J5WNwk4bUciT6Le?RG6Y14nBiWE`uyPf42lr&Y;yWkce8(RFIO9%RiG3x)vhT! zyf(6BmOhD>;|=VI?cFC)RxMt~sDp2f;n&J}XI9l2GUC(2mF(*$YZ3Mf_{V6h@aS5|(GB}*VhV%VC?62H1x3pUEUl^cW6FJ0Xac(H_}!E|EM0qeP)PsUGOWTOdkXJinN^X_AhcoFu2dJ~DwEW*c$&y!8x11$Y_gY+S4|~Qg~+qiGja55BIw*qd*pSSL9XLdswGe_8HWGMWA$%B7 z5=r?%4uBCCv8|Hdzs+DCn*TLqV4!F-`hNg zX+|hWvPZx+n^RB=kA~`AmoXw$--KG?@K~cq`xFG}WTTU0g#pbQ*xNmA#_GG$+TxOG z!?o`2jh5Zi!!3*ovZrx`s*7@xk=wqlE?&cVYbW{wH0EMH^r$8IdLrvSBUqP|LyQVI z&h%Ie=?ILSjLC2)b^4gy;^)4lqlaZpKAc_^m`ReQ~3G z9r+qt2oKpbIaGxj4SSo)#@+I*YpQo6=y7)OGlm(IQTTva$vnjLCBAO(b&@cl#*w;q zDNbu$w=Ggp>VQx|I>15>dKV9&QsxR%QXJ%ePe{nZc|dEzm+>nqLY zp+nlb`+ww{(fa7UGIGdaM0K*GUlFd9uID6ZFO8rJ_<;ANSwwd>UFq6ef$+sFwHL~O z55EViv)ad00N*Ef&5J}>;LS#ME z}WM!;&!vQC`c!1TdD5L zxewHFQu5T7xuRom06YAT!cQ}3^V}AWA#)QUOX++wF~_b=|GE4Z*JEDGSI)K<6pUQo zq36^(-Vf|FuKplr7@fzf$^Xv8TM=r5m&aIN&$LZ?Q5Dm#b?TmVm1mOV)M?vzUH6Wy za(KWpTAQSiBNTZ@sZ_wu@@I#8^<@fr8-BXjpmEfvw%bHnWwW&19$z)Jzp=r)@K(@v z)wBCvGr16(HR(1E{c}@Zfyt2!+u*lbHB1@)Z$AQR3c6#vi%PJA!8A(`!!kJd42#v2 z>q*ydye62@{*QtY^CBm!If=(Yvf?W5LnFrovfy6yR5!CsqJi#2OgWC}| z4ylR*zPL2(XSq)TvS?~Xz+w={T&z5PdFb?5KS zU(ES9kQ3mLnNES5Xng(Q7})X^#KswfZqzC-)a%eQFJAVTr5%m-jsyPU`^=;JC6Zbdkkc zYw9gE1=4Yc9USC0=!b3GW(+BY@@SR-QE*wot}1+F%eH#%ftFk%rC?1v z)h~y>b#Ct=*)wV|^Dk?Tf@%{LOF=42gUJ4epbTS^Qvrh~J7O&A1&a8l4QPbJx`t04=w6e_% zlWn}8zF}{^Q3>w|OVQe?@t}c&nj>&Wvu$;c$x84Gu>KIPWyc_d*cR_>K$H!dCLL<; zBSX`ET3@;E6w!iz zvM1Bu`GS2t_M=?Zh3dOZO#glYrp!MTlO1Y(8{mXa;Dw@UiVp`Kb!stI=bG5QXT4zX z>lvP%Q#wZVHALJ;MWBRbk1CJGN{o)*Jy&#W${@2@v5UD$hn1PVR#g2*QDEfIQqkH- z6-~l2hd2V;Zl6qjxYlGunQh`KyMp~I8n$ybyp2|b-f)f%|Lmn*5f}wM05F5#x1Km_ zcl)F`pzqwSe@{R8cW%DOzKD49ZvZDPrVzV`+f8x(d+WWh@Dw&)+8VKh5?=1GfAvUDAR;U0 z674$qth|*cz#BDK!QpDJMfdZJTdKxoV1xS=C-`g};op2IljgN)LL;rRal^_TEx;R; z_jGbP^U_n8P^+0vuTNShJrNs!vs|lPE+7E%C{=^@^I7*?Bqo+>q)c1bh0ZkU?)&qp zRjdR)-uj7>Ukpsq>-L-%ZbmZH)7vT>MW_5!6Ixf>%=bB4hMS|y0G>U@{UR99) zWQd__TOK^lUgGN<+IHk8fTKTT8zyl%&%qyKwAc=h3ra1_+9yYv$Sbz98I)KD4pmS{ z-T30jh|~F2h=7Iqto8sBNPHjMn-MzxoMhCH-bH41OF{eS7*`J?`wKyxi%g8tV$#1$ z=ZZmrsU*n(>u;T-wcyCVfD{@z6XlwQpdmg@xPgD$J2;D5Wjl3W7H8E9^1)m54K8#q zBB1XE3DoF&Mcx8#iu@(qklVfjBBBc+GwAAra=~`|Jgp)hW4TFk;rXE=o)aN`JQdgf z2Dbl)p7P>oZ;yER-NuuVe@3qUGE2NNIho$_$_quq=z6~7hMo+{0R-5%TeoJ;Ow}dm zGE{if<`7K2)=wRb<8Q>MNM{Hd&$LrGeG-dI4N%8frD8cQdc>>G6r7!~+3sr(B*6R37{`c@kO!=^3J) zg3&F5W5iq!)2~d^dx#0lC{o=~@&Lyaz$-h+f-gIVVM+-n=JgwaS)QF7fmX1IfV_2y zB#HDp+Fzy@54TQVe0YNl@Z6@MA{2hfdhjUpYL1tHRCXYWipQ^Pfd65tb;B*I5BxrK z>!;Dg-*VK2%B&kpxDtPO#PG{xB}1bC0Z(y12g}E?99a@IYacDMlpwjV(WTgV_FJO^=3?p^F@vO< zrbYCnA781MQh_vCBdFI-*S68;x536k{in!2lx|#RZ*{>rkPY;HGgH1|Lx_%B7ZI97 zXMc~RHd?-c)!PhcEf^tg|b&>EOB=6cr~+Y~$S?$LPB zDX*4eNK!4#l?S;?vxw-`7g{a~6>ZV6D@a*l_b6@`VChiS^aF^Sw)p6E%siI?AJI^b z81az9^A>5;;)aCk@dGwuBhXQjFA9Y4$VgEVOX+WThrf1;XS-tit*e^;A9WnF5j{LBz|&)tE&YXo?%@rBH|qSebgFBraV)F+%hBU;uFaNK1UmH z2vNT0)8DhF@)JS>QariRiDe6$!w~i5SSZYHhy6xWt{s;@spbAz{CF5)ubmDrz1;t9 zhV~TBIobO%Xz!lQug8a+KS{z?V569bhY#ETN?aq0kGj?aL7}JG*9`|e&R}UC^Wa=! zSFRG9l{`Un>S83O7mBY$+o3)J&-73DdD-1{-G=v`=w}VE&+=_I&{}~OOEnkyh{Yu6 zEwx*7<(n>n(ta|Iv1@v!Gk+y$ieV4ghfBX6Z zIO@}DKO3JwYe#ea=@^w?g)`~JPpA}LNGD!YMqfM!zNZ`M9@bU9ThwHlJf-{PX}bsm z*O9{CC%x{_G&n6jd8iu^*<|oR=A|Q@)GlTqWoxuf5;{CzScjo~@r{-e;kIb71= zgZYN{*&#haZR}AK{n-eKznuW2Yc$DV%^+X=h51KAm3dUtA@BH>vv zj=}0g&uxWYviCYKf*v8f63Bjavc=GS85@Zh_KNJ+@{H^-Y-hjV5UrkP%pH-5jDgx|B$h_ta3C`n!4IQ|^=zJ>T%YA*;oi;Yw{N4W; zW>;RDTg}bS&BM}mBxTQC4M(qK(ECqp-w{lZlCU`iU?$$(4GRm_U|Z7*W}}s8`5LDO zC~@+DixhhjHu@;tx#He0`2OJ`)z*%sL8z#1m*CzrR;n#4fyo5(8Rb4V3sA}yk!cm0 z{zljuqN*gWmkEj$fjbBbEmTzYx@q%YWa}=sJW14WY5$<5R(pu&TiD@Vcguv&C)<=uxI}KUC3)O`icq)mI_Vr{xyP%uADhuxWyG5Z*ZyTN)fALzJ zECcKe2L$l3BN3G!=s*O+CYp|8x#&K;fJNT!xy$y-BR?2JLsRRAiP}+V9{81Y^~|f> z<6b;Cef8Auv^rib`ItkV-sGmtP#ns{OOoA!Z=rrW{TxmbxImK`5YEJB4l)a@*l&n; z8zT}2DaQ2jnxBds>9Rk&f!g6<_$aj6w_`_cRZR$1-Z$}=@9X7|MWY5EMunAJ`-e8*7f?|7er*Y zFOx4qjXp{LpdKE=|L)A)G^OwFmxKz{#DTK6y7MFQn}i)A6sy1}{bxYVQaX8Rju#*l(Q zwp+%afq`sWy_AfXcVS24alHxUdO35<0+CW!KjT*tFJE%%sIB7(W^(LLa`_AysF%z4 zLs}{@id045!TPt?1kf8pYoPsRb#9xHqLh)ic<~a=R43$iKe}qaIEZXC3qgj&v^=m-z0f$09DqHzueyH6;pw8oAr5yQt)BL6R zm9j(jEZgm>zH0MR&A`vk!{rXtaIXDTUc63bqMg;HzZT(IiC-;u2fF1iT@Is=YI1+;obeB zUY~}$A2>Drd6O2c>@D%|O5BtD`f!Yv>C-Zm5-xp#{Zc1ZTEDdCOkeu4ePr!aXb<*1 z?o)pLzZ{Ow065gf(7Ox8i;scsu0V#hSJ@+1F(Kzrn{(_3)b9+h<<_l5n#g>hVUJt# zb}40g)_~FzE9>V^=VW~6%s0Zbz3?hbrPnlBf;nkTB%hr80{c8(9ocWwO(66#=RGRO5oPx_3rZ6OQm1CzhG48y_Vb7l3g6K`CiDK=u^u(aS!W7 z67F=k0Mmyf6O&>YNzAHw!kxDr?-ALZh58BFf-MmW%*FY((tl=HmT)S@? zhh=;DA2qaeJx&Z<)6;$wOY`MwuP@b4+;<{X&+&r8DEZHtwlh~$$n_rlZv#N=}^i%bnoV`*#HOLy7k%G3P1L@n^WWs5#46$ukeKQz?$xQ;9s+rjPZS}!W% z@c;r*$?RK@`m`Vh`tQSk)I@Y>i~OT_ZxH5)FIiUI$C94l^L4`z8-ni}v2GIlTy-DR zEGP5eGYfcqWEi-nx*wMvA<7rh;^tQDdnvUFJ;Sc%pVHYjK4bB2atMbX?HXT6nPakJ z%QNTQBteNKOQll$Px~t`oZ4l@=>}XZ?Dmo>cA!6?+~jMd7Z+K9HbOvkIK%WUX@0reJ!OsXX~LZN0`xJB7xF zNjO>dxA)m|1CSRV%!Id9brd^v|I}^4D}Z)$x!%e2X!{uV8AlijKXTPHn>z5^?B+3s}WqJgwE2!SokMlkq^VA7Q2@ zX2z+1V1RXJIL?1>`};oj9q*l1tGu^vAARD%x7vvd+!dtf@{D~8PPIsqb(S&BU;9tg=I9- zj0=4G^dV~P=6F4(vdy^-J(@t;Is+6kA34Y)yxbv}rpaw_!nZ~JCGp7XHRfLOw`B|$ zSZ(gomr$)=qWA@%Sjr4DKuhmWDi2J*1UxAA0kgv?Nbd>I4kRzk2?y!g)NgBD(yM6= zKFI8q@9HJ-9YrN$7cG(**F!VGGs6{w#eJ&dca?R^9he-pnh2JD6WKWzPHP*Z={{R@Mr zi1a3%sHimQqV%AM2&i;I4^?^->5vFWl_DTTqzFhap?3&9Nbfb2&^rkr1PJ+^egEgV zpE>8uc@^GdCQNp|``YVT>$3o(p=*nXZ{p3ej1_luw4a+h9x~z1C8H~#Gl+$@m)8DA z!l*#mo+(rm*)`dB!X-F5CUTg2h`{rO6PS2=!B}8|X2koWV3OFdEKHfw>$zSW{)5>rz8(palK0cUpkR8r-r=3o!|Jl-WPYQ%i@=`+9#;VrrvHv%>`3{v&xVGh<4zZP%lW z>Us(abOK^tT%MJI!U-|~a}6hVV{c?iD+IT-V0nX1M@V<#=jWBTmQ(>k2t(jOms~M- z7xUUyx{~@Wbwfw1t|H%oxry0XywwUdS^L>^@4NSfXs+RFv3S6y|I58F@R!Ez)z49L z3eN4eK+!gh@gD18@1IfDeGAq&wp$vWPA72)43vc6jXxK{BzBo`{ZM9iVWg8tPPD)K z$GaWD+l8b#Q4k*Vu6>Lb!CSgdCDWwEaLv`?7S6t8=_UZ)7v*yjysg=q!`Zyx8vL6X z@R&Q!OB$EABx0=WB5^RT8UhwMZS&wxiEv<&Y-^T-dbE@aI z!`fjFR)1^;{Pyl*_ya{vXG)}JspTptZSltP|7kGGMO0 zRY=1B;?)0@*2#98HoLD^T-2@MXm)B*_Km6b+LgkbTv0^d$l?OAuY~vrD~}3yX4LVf z$)0@ik<)t}GLL&MAvRq2boh_RK=$SepH>&RqrHMjYe9=PX`52)d2})Q!4dpt6JGVSAb0cn;m}nM&O>5hncfd6$gV(;)Af;up|cV z_ix2z;GqC87REK;sLs8BUFfctQJ!?~Jm4(s$Z zh@qG%LW0qviLyZ5TOOdn0(AA1;EBXLu2I4n=Lhwrqe?4n9sbG1FXzF|yBoPd-DXr} zKPZXovH@?irenNwLy$T?(Jn(IcE5Z{96>$44-v%gUk29^zvoz`!Kx= z`IT?imLmt53^)p(f0S(ZDC++$4&1(E>Of2$-S$bF=v82kx*Y0id0^-*tapL=akelq z-^PE9AON?%JH3s01h>(|ztv6Gy#;ZpkwNWN2NOVEX$a`)XlSZa{i%mB>DsTi+9N3S z@lO>{tj&DdlapiuHHe&ci++2lglGMPNC~SE(glU)V~~t zgd>*t2q=m0$M(X#XaUm(wzAY82Y1u|BT>uefr*R4MaIi}S*-wPKD2qm!0=cQ#1VtI zsxx^a|BDre);Y9^dspN9p2l#(+D7KJ zwnKczDwLK$hu1@Pos>SG)(@jF*S%g)>4-zutt}x*TV5h|Wr&Twt5>IsVo%(gzwmGt zqBfwDCb~P;hFo%)V5a1PV^BD&UyDxG^r{>byLv!t77khkrsfL67zvG4d({{{c$3DE zu^HclHFh(+fFM^JpS-zsKI71OM~Vj=3c(81X6BY^tR0v?_0!KR{eFAPUGtSWOfS>5 z{GaSkf_hQ4-1+MW)%DPAg zL5;JgxN!jv7q_FwEqzZ4<7B}YR{)=Gj=Q&tseoidpam40LoAw*To`mV}Hl=R(Obuu|%IVN~5zatbk=~ z+lLNwnS356RX0)oC((TPmggn-Ye7q@Yn-f~N7O%R*$#u($C?a^vMLVy+T#OCAct1n z5WCJT{D^td$iqtY{BE9*}ky=Nv@L42j2!@zf@G#8qb)wm$*m&A?Wvb zx)i8t6E3k*+yIw z21~(LON)~HUT^UG3-ER>65Ce$N5ds&L`yOx+_3-1uAI&P__R;<3z+_TLzYNI@?d* zr8T-5D=gs^IlGe_$4h`cSxhB?d&g$QLGjd1ub7@%f`8=0-2dgb+h{DZwpoSz&wUWgqGciJ^I96Ou&=PNR+ zYRhoTPPEoO2Hhf#&99m`Am_GWD>m0G<82?_t3I4?aZX#}XWz0oVddJX$!a_?x6QuL z;7$S@yUd>Tlb&;*KE2?CDdqY6T%YOM6mQiJqyeoce)wR|m(xHU{QnU~qj(teCon=B z-mdeuo%jR)D1nWvTP^b^rzdN6tTKtRGV>!vmaWnMNCZe?qtMgHsjRYU~*empr z{?%+m5&T`C;JU^)BPMOvrkG{1!{h3XI4^OP!WG;3wnkr@1l{UNZVSG=xi)1pQXe|1Kx8*<69_B9M z5zS7?DGrs%=HiA10e`60)`4=^TT9_~6E$=))!FNuzcsocXIr>$7)YmU^gL4v!Ww4U$p&h&O;B$Cx5)^u$q6#4_oa^rrL0cj7BGlA<$ zBKM-i?h-9~H~p6q;lzhh7~La*SKsf*i~0@)U$XRfBN&9f#AUagT=6|fp=qgS`>c|{arwUJ&UzdVew9=1r z8Ms(ggf;UuCujOrcN`|T=|%Nd9Ih9ACQVg`$KB*9?JD&EzzTAN(zE`;O;JEALwx}|7rN#IG8QYN*_k~?Q-Fe}HEP*W^ zAp)-)jCJn-`WS6sx@{Ca?>v7q`G%F-A^;ve>lNG!9C-TPZdoRYisT^f7oyhJwuk!8 zu8fm{U~NY-$-NE5U2zhC43@dE4PT9&=4z@f7S%@^%5D3K1;^Zd*5~$# z7=zQ(IrxRS@8d%E)YY`Xcv!LPvKe=YM`tMsAiYn z&0qOrIf|Ig-V!w$s%~FU?A3;2A+1I&c9VLRt&sT5vFJQMSC{Zp%oSgN)hHN%xKwaw zxO+=B;+L@nVvE9FzHtqIv=3#Odc5!Qe~&&$6eLIz(6A+Vbs3_2nc4)utWy8ns4XjA zGJW}RXOeR{`*X_R3{i=CDMTa^n4s%Nx~hK^&eDb;#tvDMu;%u8Vh}d2FrRK-(D58bfUG zwTQfgw05NyKiied0?0mdB>@19f_4+hDD|pMFD{P3#&IdGyE(7vn)ak+;t{_a5j4eO zGbl|aXA2@0+YCBxTHyoTp)leC&|T14|5b7s|4vT6Nm|JzS!#klmh_hOTG-!x!4-AN zKa-_$r)!Q(Zt!rq)1w`;_eKmIdda4d`b5YgsNQWlP(`Y}3l#1(p*NR0@w&MamG06} zY{|pgc|KoTE7{g*rPEFTtHt(@F7N73tIeKp!rL8x33M7W3w+xzxkk-k3iwr0Qm@ul zT(X$MA|1*&$lSlKtcA>+B7|`pM`uSp)Nc;$UtFg~ycw%ccmKBn8O@}te-G|b{hX>% zZTwUEU$omYP@&JH%Af3u6?1WDVLo@SUTRos!Uay(W0d zYIK4p4-!Kgrsgr*?Z6)?+xLco#i21-TrNlVpm(D0oF2 zA5yCstT&+hKe(TKmVEzle&L2QZ<&!-KmbZ1No%nK?GF7qQ+i>vIhA>8za6phROQ(; zrXj!C`g7snPZXEn!1LD~zkV0-JVkE>_2^M2My0+WwR^3b57ntpL$VE%>{otlO#*4k&tT4@ga|xq zfiHUA(EGX;nD7iRM!Q6F%EZcp1+E_hzoKY-${9I6BiF|YU^?dd``)XCyW|>-Z^oKe zw5WyLHGQ@A_=3w1vN1Biac9g;1%^Ka3-fR7$5StOHLTN#Dx%&<$qOESg7I}*RE&27 zhWY-(YHjN9(-p41jp&sCab}+tGbXgN^d9~RIuZNlfb^*9Ws*p*xNu*r9ywRGMtO6N zr{-NxFvj(_fq=ztzq>5|;?AIE>Pp%`l2?#13w^~?aFgQ`>-ig(knjjutpCr~%(64J zDhVim@67_6`Gr9nQ>QX_<8Us|~F9PHV4eo(A5TD(H{fAtzE?Tq!=! z%LkF+gLNJyTdT0w;~vrkBef-CZcOwV>F2IoV9)$&o``}r^}fd~_7&j0(utmgR01_V z%Xej&ZfA+NGJe(Ho;)k0cU0kg99ii46;9_Y=Z~Q@{Xj95VAsbXk2~RC%SxGq0RE8o zNGjeRA6a1%f8n9pfD#mOwM06k|AJDkntmI}bA0l8(sMa@nc>-@tnt_RT?K||q4nr! z7cJK(dRUpTcOJmCHA&lJYJ}>{s(X`-*gmLQHhVr_!$8}FTl}4L#}xrv_Ck{WN8$>< zIrv#hL3GFN{h-?gd-luoBxbG0!y1FXt+IEfk^28!S>o6T_wm%Y0M+Iz^{yMexN0PW z;g9I@ecIHZEC-@B4keYFfgJ`({AuF?6~VYLc_D)WD+11=r?hw(jg_I1UT`qH8q$3r z{&(MlZHAne0FqMLs0&dN-a#o$yg$42OeVam75V{p^V5BFN14s>tB^*oT;KMi`B`&8 ze9Qld>E|G8mJb#&%XjcrXQ@pSwIX;WD|Z`TXO{0pK9;7qdHf{*|`}wdO)c7C%6>1IM|d`nMpHe*QCmp%svQ6mwZg zfFb_Z4au4XQS0}IU#t1%n-k@N&;Tu_i8!BCBOe!D!{0)m-CZQqMPC1%^T*3CZ&_tO z{fTK=le{*?(2}E`cBz90bB}5dFAazixL?tNI2v0ThQE3%Wh2flM3ruE?N)DLn=rmf zo>5&~s5J~DD2BNNHjizNtIjrClSc;xwj4x;hr-RT?lqod3SVgZYySy(Q40+H`}R9v zj-q{7JP`h^z-&=48{3w_5=;>ESZjTs?&w|0F#g0OAm;oJb#v@;*8%ia`l_RCENw$ z6spNs_!{2=K5(IfnOV$wn>1I+l2cjE#OKKhWl$4m)>%my-uI8pT#T1{qVTJ7d2m$g zgNiFd{GkSd`2r?Ef4w%7t)Y`V`6u2@#!aQn=Q%?7DiZ9y$=CmPSn3ke-;*2FOp2NfuAKitT?ud|pwz3m#* zdL(UH(t#L&Vhpq~hRa+f`#HGh!m(RJ)gm{|45?B_6hWM%P9AUi)!$>EM20QaUxh zWpNZ7E8Md?F$n`d&lTrx#!Jd&#d?QC&+Mnxxu#X2+0=#o zl@V-4-wll>2;w1bAAjn6EC}RwBXf3+7@EnXk%hdtGJ!3NI#!?wPw^CT-{&(%mv&Xc zQvWF!5SbU_CxOgkyaiX&t+3~=zYu{aHY)hM+|2BXs6)b5{BdKl<>qzzhjWe0SD{v< z-)MXeW-={~&jDc_TPXkj!P9p?QAV(c^Cu&0u6)+wA22@PP9`FbW@i z!oD}{7KSQH8C?&Za}U4Nx%oavELi4?o2*`UucyAHE{7r)JsRxaAQ>Ah9p z0ZEgZo*f`_ww&dUo=Gn{QGR{;)3DqNoN*KOaVgq|(;vc%2Fc|E;L~pu+MCysbMT z#dG~fGQvc%_(#VPqM6)pnq?K4v%p^Z>zCIKI5M1!4cs+kqe=Q<;dnDyBK&rfD_&JH z4zIl2u?M~+LqDGTW=WV3V4bQzSniadZd_30tYx-Y6;t^8W?a@!cVc1SrkMIO8+}bR z2AMBj>9G5w`u#1xszk{t$XgZu&tEs&W7UuWnF1Xw3m%g{YPSfh$w|)_Y_Td&2+ z=(r%*rwEZmUDqGd_d$4;TCHFeN2RUrr84m-HAXwC!j}wGgn=v|BlS0}VTU+ox1_hG z!gG3I`fC3S<+s(M%w$Q=!Ouo+XAu&&XGDK9<{l~QMclG$3h3IQ#Di07 zcNVr~5t1=F2CwF`MDRzQW$ta-QRFZ2cQ3+-y{0m*0-CQp+Aks7B9WgPZS>iwA``^= zKv1=dlHN>qLxT?yZ&>oB34*hKPnsIknw+hMhQzmLAiF+JM#;I3CB}|2&fUrNb=t1= z&GicJ#;jOpXXT=Z;cyEmURby!+Yr!|DUi;eCFVQje!0>=M5N`qkIct2*|JeK%$#KV z+spqH3ga-KCmxNL1fV>NK?yo1L18-k{{ZcO6ECh0V0w(!pnwJ33!al4xeCAQ zv(@DVlfXL`c9d<;XqJ9Q&okim(Xd^@FhOzfm1+9k@l`}&?-O| z>TSkKbSK)AdtC_8JgQBMRZ-0Bsz#({u>zUz~K8xY6W9VVY>wKRRI)tfYy zE^fcYMF$Pb1}gi|-rPa@@#fvFOgPsVqK)CEX3y)c9$vDygt@j}(g>bh8DiZUXB2uo z?&g(-2-R$n0A`rInb=d=pIsFp+&_UC1NVkiP6nzTIWm6;`>4|;fboaaew`h}E?mDG zJ`%vS`f!JE!jjgOSvF2um{P<+|GeEBMy_5WJ6q}Ja!6UPJj{%7rr&%X3B+CaL^X*q z%a6_;-^m(~(!2QKBn3=78-*E`pY1jkEzOnq*jRZu<6LUZ7FA8#(`m=^zN7k*+O&dU zA!((L{nP*12j*^M(QVUW;Pk2mFT_+&I`>Z8cMgb04zcr=&1wl0H_3K@2DVY6H1>HO z93yM|ZJSVd$o8k|C(DNG(S5MV-HSB7C0>7yti=)1i*wp8=6|g3bTN9L(vwz-RgyBI z%|q|_(ZS53en->Y;j}&uEV;}99u&dE$^S?wkxp4({7?i2kF(Av7a~GGTll?Sh`Tou zLT$(xx)<0PGz1L15gY7tXM}JbIXd)n`RkH;Y(W$-!vKaO`z0}K%-Za3lg*&_y?XlK zF#e`q9S|UWpUTPGm?h}Mujm#$7W7&;mH&2DcKwt11l9qs`Ot_JS3o>R?$7N@k3r?e~oze6h{M`rn}9U$HNBOcdsK+w?UAyu1)eNEblyBJYJU;DD*SBQEND7xh`HwM^i} z-HKhWp-6qh1@d#uTNB&bKtM;U#X_}94XnxpWd|7pUdf{P8f zQ=Juk`pWFZin5d<)=mT{Oek+fo5z(8ru)+%^FHF2FH)-xvKwaB%1=S$w53z^INn5BjV@YQJZHk9);>$PN8{ zd65*E=l8U}{~>zIr+00b^%|Cx`6QCwh<9;zM#>#?E3x8~q;%t8xezi;}1P+b;Lctyqm?L{RNA9Y}ID7AmfS zSqJ-E=?y`R8j;^ojIJ5OhHb4Sg>jfif3sURCVEd)`rxYuUBNTxvf3VTa`qNI``mY>XhNabPj zYRoKie4~sMb&sJ=pX5uQ%{-N7_LKb|E2K^Q^jl=V^izG8(dPt9FdFNRGj)$)T9Ciu zO3C+@x2W&Zn5uN znefX*iXG=G*MQq?)l<)8{Z%tB$Da?5mH&$gi5)C2?Yd4Q`aigyDvD<$Nl+PGMQX2^ zF?X2rk0+Z#=ijJ958F^N$!o>>n=x;<>z<+ecw0&Q<~Tzh=IxGQ|+ zET)cToB&i)P!|GY0OBSv{>FRl_V2y^s`P^+?BJqZ6KF4(2VvD*7g#W${qQT*o-){| zk@4;l$xP0%q-3OF=0eEEAd91zuGPs+HvabF|5kG}92A!CMeIPC1`(JnHtPs&X8 z^>r-lymgPYC+SMl-!U-G&KA!~+0lNju}scZJaFzmRpLIu{6{;uF?vR+3A^b4_*ER3GWIf7%o4E3Yn1rFXu0em3XGFT zU)$n^8cL3I5&0WLsoXQ8$?{qdo)?dOUWlJkV8(Yks%jpOEkKXN;?8|o5#o68S~E0) zo3gs0CRNUxXVE-C-ST_MuSgCV!_i?BRL^cD_r3d~i(nhud`aiQE#@t%I2#_u8TGH-nS zcXB`eEn$yXr2u*>8Vy zpU1kxM|+m{=<^OilGy2^gh{V{_i9Mu&hdEc*y%2u48OJ7)W1_2E?_d7)&zcZDyASD zpg#(u^LeD2vyhips`XUeMcV7B%FS~#alq>V+?wfH96eSKU~Ao%r&lS`R0jz7 zV@FYdP(Ke^v-yvt2f>1N;D*j+>*+19UVOTZz8>3w-k-B?N{J)9Zeto_IR_mty>E{x ze!<|;-Ya;}qP@muDS+Z1@Uy?9Z9k&aHw;adWy1~Vf99sb4g@I8K)a2a1GAJXr9ZkR zDQb0IC63q0>8#olpsQAaf)`$O3$PJ7GiFU^!7LZB%ub6<(cJbuO6*Z5LzO`{)1qhx zBY4z(zGV}gn#*k#P#z^VvH|_MRhyk?K7E6+RaLP564a)dUZM`*2IpA5`#AIAS1~*y1*}1BEO3~8y=J)hTaj35CK; zCV%uK3nrJ}TMl^t;H_FBN|~s4(r=@!C?6)@Sd6i6b#lj1J)Hm>BUgZX6sL0=6LGH2 zjAG`nZRn5B=HmXk&5CcpPHP5Rdgt;2qa)0%5q25*F2iZs& z{HIq^DPWwzJ_O9o7vtp8Jor?EpLlzLd}~` zhB<&)c3JXdBpM+}iGLhsU(d7l;N90~kGR&XD`2}cDpURwUU91i&KDG2Li>wzW_X#- z+4$YW)}*8sS5k{(@I)d*&_p^e>cUB$zb^hruf%GL70UcOUn67ES#BC_HiWy=2}pw=9hgFD}eZ#F*5IXwt>&PQtp$JW|g504HunN z>@ZUv7LyBoX_2%8edrUSGu@(7Hn8e?Z&9Pj0RnDK@U?#P@#9hRVGv5W)JAtDo%7`T zor~6UxY`vb@EjQJn;(!V@xo*LMlq|Fd5qg{LT**u%U!`%>~N^$`tH7bug{L|XZgp6 z8w32kjq7!Pz=tOVF&w+E0Rq;K-(9J2TdXM%%%|P#>vY!Q=2Azx1@Q z?8n_LM_Yt6oW#D$GhGc0QVxj(uaE1eg*$Uwl8&EWp*gc8z+v+jJX8E*{}0Ie{{q() zBx<|-JT5fJ1c+I7uEP4aKF8fZ;c+s%?MGzgKHVvQSLeR4uY6qH2NL^xWH?FsEK2wL zq6#y+ty|CYC=#UP;JG%N?We)ni(j*rrRZf7Q>9zAT>}q|AH1D_y*973BsoZa+?HRZ zLvmuOAA@OC>2~Cul|!P}ye@b&TH8^nds)r%U%>xO{EsRnV1GmSIVbKy_u2oA2LHbw zl!-*Q;^5>qk)*KrZRIA_DJYc0TXii^ZMpqifi!``cYia(B|cMZ^QCBKb@yIP%yw~} z@uve$5?@Yg*ImCSMFsYCeo5EOO1B6fjcsi@`y&n*#kp5x#OuHj?A_n4&DRu`$G`C8 z)ZXjdb)<03iLRlm66_qrONFNeKqV@@EU$cHSN&4u6(nq8ka84q)#hdIQ zCgEslxf@Cc;BX_tDDV(xwu4pbiNj>wQ?v#*qoppRPQ@?L38LKqjPc+k!oqGn{ZMZw zZdGazuREs7IrG&_%yXf)X8{j+k>fySIR0SDDNT&L@v22{vMKq4P2wO5Of0_a|AcG_ zKwI?p*qm4>7|fH9QJ*()*Xpa*^@(x6ef$y>YE1w3=nMXKp_!6UYr#Oh%k(v7>$HrL zZ@6;7SWdk1apQu?6GV=ZC&k;@K0{j8s}=~1$M+fTAy>22#X^z$!H%Z7zc8W0u7snL zNy@b=?}=2IQviJRi2P6dw;$$tRtskQh1(+lXAQ-Cey=Q7nf;k|p(Xks;@(UP&sJCl zBUku`shKas#Te7+UZ4GP=Bb}2TF&1pD@Se={gaL$bZrRC5tFuM!o%}p)PAgg-UcVR zSKE)jzS}3UbAX>&(p(jg+~1nL@cXH3#L)6C;?Qg!m)TeQBbS4n*BVg>L=;moTWydeVrk$gUTSfX}RgBnx8o`FIIzGur@I=(7< zi|{!IswU@4&JtTJt9_O=6TP`~Kshsx*!s1Pd8H1t**=@NBbRh*Q)x2Lel*`DjQLpD zeWc(tN86b3dU`ikYGFYd@w}WKx+q&@WerPSa&Ujtp}H9DF%xw>5}6i-_VMDdYvTCr z*hd5fyiHr3`y8XYX_w-__*d#Mums-2o}Ln8PoZ@zZOKxgyayFa9Z3Fp%2eX<2=dx4 zXIA0x16;zeUi(1m>zBYwc$V6{s^R`ip5ja}`&zB+*ZN*Rchs7RVPK$&vDL2YTpQ{= zz6%SS$dsNh6r8^jVHrH|(&&|}zWBJjfEPI0&idQ@0q^baEkvX%`LbN8qlmImmq%wp zfLK!9VpuxQyOc2hoJ#h<3s8Ab#i-s9+JZ2mpqYj}oiVkt_I5|_3o5}vpx75w7Wh@b z%>IJ&$F7GH%=6DKKAy&#cD%MMKQed$z&u=@UBcDoam!yHV_otSl!kxtD;P!FP@p&i z9b=<&+6Z(#;n49`r+RU>j93$WjDfjrI_D1bW1BlzK!y)0VC&xNMSS01+1 zyCxg^O3CKlHwO@R&BKD1)WFdBo!Ga08@^Hms{R4S>cq_oQxhM@@&Hn!eiZFdeK(Vo z`8w#iDBC${B#Y2_CA2@OG<~B1T}sLu>Tpd9yx$Yvp-6lPC=T*%FHmbE)X1~AFiBhW zK)8x|uxv!UnqcUB*ST?Z`$gLk8=|aCz%%e2LcT-rNu$wz02(5e*a@#Y>Kefdq;zbN=MId%B5>?hA+47g%|eDnazoi>MJ zKI@O>sLg&`N-QsdS&2xofIV3Sa*RE|XG?*#C+1gUOnsF%>kEweMI4{dvs-JcYO=ma z`$9!R4Jgm#_@Gkz%m-CCni&5lN##4Ao}Q(|OWcl`Ng(Z;r@bK0^W9^M(9d6PYhzic zk{J9nYgyFf$+tAPEVvZ`Y%a79EyJ zK+kk6S7#7|7&ViyLkm`9I6+0vXc8HPts}XGb8+O|-{fhYFep2^l)rJW_a2(Sm0K>#eQ7houn0(>7RtH%d1ghi z*X6R_t$bT);ZFiY1G!4vVk(ms8t6OYnq%<u+SRD`MygZ^`A8i}O(4z3^Jpx)UtND#+p={(&WZ2SxrF>tex^gg z2IM-rWk5T6bd>%t>J23-TPj02iwVV21V_NRPk8^WBMm$!^y_>iNl#AnGU|c`gc-D@ zvsw%5+TWMfjb-PYKo8@aarSbOoJ}rK9e;jMkrH@8YIri}jH~d^E#K&>q4SY*jq789 z^T-wzUtjmfy}z#x^Q099Vg-lR>LyFt?s$>$|$q15Qom11R$D2>2LnsFw_`|B52PjbJs&-%9I8q$Gm``Y5Gj4Jzg^oLG?8MiYTut6 zzTGi%pSf#fQliWdEiB`&W%zEI;})M2r?%F39>v6HEhkbmMh0xO^zNZk@5_UQ({jJ( zE1(S{hb?K7Xv=2)sk*ZCatiLhzXQziMHtYnC4wwA6Ir;^BwPVGcux0jz*Sh{?+0|9 zV-cPVTYj=u&|@lsfra-B=X)*WY;7emVZ?g9t-ROFC~qFWVSVFkwNNnLw0wi0h_mg2 zvf2ZZa0|KrlV3%46L~#J;{jz%eJWaLTSUFf)ISs1Zm}*)jCPA}PAPFMgi-l}kdnt7 z(!q7Ed6i`!h0{L5WW}8}p6)HYu%JzEzBwAJw{{W7x`soL#ePiS>UyB@GBb1PY~}!< z&)uTN0}{zCGTifTzJbNY1s8pZTX7SF#0uy3Bd*S3`n`!?Nt|=e# zv+Kox?!uAY^p5UFZQWbU8;}%$3q6o+RMO&~x>}yvt6;`waM9y&B~i{OLFKRMr%8Y3 z0GDK9hXs!?S+^I-}VeYm#b|wW8jud%_CN*RHtsn&A-bDp zhR7w)lrXqGCbj^ue}lw=EmP{s2UE-{ax-W*Ge-z%T$pgm8^pG6L!rNev&28t84JGg zaW=5L=F$3b6dh^dbbXN5KqxtL>@s`w?H%s^;=Zj@yH_W{{opvrUI3 z?Uq>#@U1H2y)9Ni3)QRSv7eurHl6*-->q-|UbOMjnXBRZle=}xLT^YA6IJFLfmyQk zz}A%8`y-ME=>VE{G{FY}fupda1O0o^1Du7wOefQUF`Idg2v8|*wmoQQ#6p5LQ}&;F zJWP1&Wfo1U3E37rp)|lTuh%=~+A9T~q?2pIwBf)s>qmIGwkz$$Ag!-iJ{^t~35LMA-92Zt<>^21Qt025YC0?&SG4E-rx*j9;<+|%X0`-$hTSZp(8m|KzDjB7@63@Q9bdG(fyd6Lsm1?s=bh-xHrH8?$IA zxWWWe7=Kz+?DoI;Ec$P+St2WT$=BG)7of^=VY`;p6vk)SKq7fwfQxcdb$a!!5-lt% z$?qWci>3Bh2YU#VA`K^`M%>`puJC;r-pWI+Hu{yGfBuz~9f8{W%q_x-wJ5u$sXl_2 zhW4XbmXbDqYQy{AFD9Zc21FuV0w1k0jYkTk^cW5##t~~JEz>`>(+*tfMj&FPN+eQO zvcqU7PEr4gP)QZN{VE_I_%=9{Pw#Gv^BJ=eq?P?+j5}PSSmkc=%`+6dYdrr$|9al& z1^6#7SEHErDc{pyrs%@`tH|^_qrvANjT7f4HgiqoZT($nO1#U|8Y%d!RL@$bN3* z+0E`GljwY0V5{48V=j)Z{~xrq*YRQcP?|Fd^Ry37_c8uO_w>@k;PK&$gYzJ20)>am z!dRjRBcZ_MVBmV1H2gkUt-#OoZ zjPK%HWaN%LHhZ&p*IaWwGh6!CED5z=%!{l!hwrFEJ`JjVKR>EAl;&2-Rj@R`n^VRxsQgVY+n}6S*)-u)hj-|kU^gVl z9`DGc(J&7)KSvO0ab?znKWz&~254WOxAxQx`JLIk ztatki-7}IqDeGIg%m3*PjhdQx!eJ_UAf2f(8wHB=GdFN9`mWh`7%K1a`aMEcb|2Z_ zv}A{Q&%@+HH&Q@_MYk;pTqokme>z&&?QMQA1%_E4t_m*KG@%MDa3#7!mYkJ zBI=g{?%Rb_biYkcRQ`wKt2-o##QHzJ6y_2~^?j$PQq&3C69a|c1#g1?QCt*%Zp5^v z!UGeE@$`#n?hfjQvO}hHbfHhx?NSV2^%NM-^72g0kM%vC7gBIWoo}q?jFZlIA8v&B z%dp}C(zBwfyNTwy82rhQ(f!D@vH$Rpwtucb606L#KrF|{*~MkFjcQ?R}d5*a~YMciF%+utWWHP9$&hD z9Q!vuOHHVOEgXw&AL3b2o;yOKMypNMKM|CjD=)+Ldv;FuCx2BpvmLkFq&&Y!uGt_Y z>MR*M?s?w#%7b;r&M*d?b};&lREJpcFU^dvC*>B3hUdZC6yeKxAXfA~;^c>VRBENK)hr3{cJkkbHx`7%}!cLVz?#M-EM z+1t~Uvpjd;SnF4OP|};y>Z!~K!KNuUA%f@Qz7RAWCOX=u?9`&7<}*`cyo>vH(kmUi zH%dd{xv(2U_m&vqCc*%uU?ZpFu@7I1Zyo?X!fsjPZv#KSc}mrg0G^dcMoMiYhUmW3 zrdxg|)0C1lmWb?d*vGOFVUU%Ze}N|>0zKC(zuDIZV8l;Q?=CdlOcGQTX!`gZxlGp} z%*M=l(4fM{){93vsb7^$+G;N4Y>VS6;p#}An_3&ek!pG?I$N=WzYnh&{r3KG{|WW= zZ*YIa5VE^DeTD1I53J-4)~5o2)3x2uJoXXJasM?eal~jO&h06ivKPE>!)b4bjDRru zELX>^ip6kwulI+KFn-&En4tTc#}(&9kEQ0Ah_+t~#zt$to>evv?p>;IQbPw)SZ2Tu zyVD%l{%kfhKF`SIwr>hR4azo3TQ0h)=XyXL7mP`FTS)`!P?CTC;@KvJtJF8T0pY z$mNU>n+baxlF?^&#?WV599pHnQRzE7wv5d4!4vK=h))41W*GS3tLaY{?(!bjpJ||! z%;sW9KPBv^YVx(}m&TV#V?~+Q`>vir3O39iLzGTP63%-w%4B<53e#bfP+GJg0Z?VD^6{N#o z8(&3#?jy*Y<`UGrOMj{rnR*%| ztuKi%^2)%s(lOt{B(|1j2)K~)KY6v-=nq+3biOruPtyZvGzAw1`ly66#S7y1M1due znyJn&Hw=99mTGoi5jo$!p{Z{*ncjueL8ISK)TO+WRk~vrhA-~;BWe731;mDt9Z`$o zwe^8(gDc=44(`F@8l*}py}SA^Q{i7hiN#a!E;mWZM${LYD4&nCazYUt74J++;Muvrj5jM$idF?OQ>!kVpv4MO2# z$$yF4nF35V*YTIfGFg1oino1fVbUE)YT|vCxX_2r=t(J#X|1$Qm$gHdLrdoscr=+7 zPq|`=NxfzBF0-^kADGy8}kIX^j53bK?OouKp)J;z<~AX1JfL8tnW7ut}CY9~b9( z(i6zdVanr{k@%oT?1FxJv}$1A^ZfNL1tVf0So`xK>2f$zm#U$yfP=Z~_{Fy?%=dPF zcBVfZJq$H0)XB4A)a1I@gfyNAo}MIr&$IThD0F=m$Nn_lyN3smPdp1ShtVaW@;N=u zZa?0I2Y`YQ=zMVFY?bW*L%nj3Ctm;5Ox`_bul~fAzpP$4YUT_7`t9y8X!qry&F`;# zi3WML{Z~=5qpGY#sa;?`pi%a;+D@`4-W`Sae{_Ap<7nlP^UbN7LHuX(=S*34qIq84sAQG1PD_2tcec*V4vmLBw;YgO&$o;!gg#dp%9@z6tv0f?@XYrt`A zv~&*tP~>4DtCMWv0u1Fx^61<{+|Ac`a9;h|Hd>qmS~=Rl1a9Y&7X8gKG!!P_v5Xa2 zd?+>Lz=B@0!I1?M6dx{U85I@zU08Ao$3W#qbu2))^VngTUzeBP(3$s*I66eTuKdjT zc+c^dj#2eCj!x~Dd3@3BwJi#ksU*WntI^8}ZCjL-PFJ`?(F+F=go)OohA;g(?&b07 z+!>Rzk1xq`&x4IetC+HFyK|X!H0I5c^ftqf6c}Ycwyv{otBLpW*}EtYFdma6tW1A3 zSHIH^x?%A}6R9u!{?z%TAZF6?IAr1z!ux5ud}o5fT7>D_lvoNiE|jspMrVtK`b*g= zQrn4qi2~~#&r&hvwJ}tUL@QYppABMxg<3zFPJ>1ye&<3F%nwI()2_dR?D zxEnDD+u4At!9o$?Gf&LlXUB?P`Ty;`{x5*3y6ORirT>aC4~UkOLt>si+~Kl zYCapb?whK>Zuav&FLv_6?mWf8N~qP*pZVTwA*EKHi))Acy~i@ zUfanDNS^MStdz()}B9O|# z(Hm^oiefiWZPMJ;zBQvL5-H#&fcpbhwQ;|H2pcGEc1JWuM$7Ch9aWc*Sx;DA2K>)iakKHoE{kuwYV1V zV~S)G>P}F(Tbt_P;k&n+bKee~eaR#LO>LbaA0=iN4-rQ1%~_>G&kNE6CU8A#7Vyf< zjYZZL`E%?D`3JlKrmts=CsUa=a|+> zWnG|^`F^}0ZCR#&tP8dKv-3-cR6v?utdK6*xV_QGqqOY3dl7h2QY3Ko_eM$eNVJVq z0V^Ir)2ItLT^?He0*CwV+_I$hRt^2(Of`zuBm#;h_0VXG9L!0YKYRereGkR%aiCaY zIeA}7xV1wh`x={%gL3-`kQ#kARIA`e%)!(2b4JhHXYetN`KdSfz)m!4%Vk(Q3s30v zNuSrYc#%cH@ja%*Os6wr&z&qI`SjaHDA5YAE%7OEp+QoGs{Ea!S=;R|7y2nNLuMu%Dg^lXB-=lV?|z$izF8sNIVL zgN26^ZligU46j7u<06IUsHdokZL1RhILLfn12sdAV~=({)Xn<>re;hD4dDyT&FNm5S=@@Xs?DbFC``l7moia+cR305X%8hb03uXI&opWXf3 zk{t**1P!ZyUp9`(yi;#=kBM_tCtYkPO^*KUZpyy>_mTFDPCbteg;rx2I^a%|^0qtaKGX5f zE^7792n5(+)!g@cpIp-v*`OoBVkh^8s5*YZ0`&sw>?`b&!(`MmCxY)AI0n+sf#js- z6$Kh%PYU8wdvT!!TVm0Bn-NBdirr31@IoNrlhUH>RN}s>L6LIQc;80V+dywNcqQH< zrFGJhtL6~^o6XdCOCAse7|JhOBnhfDDQ&OvMxRij7Dmo!&{#`1Dz3s26B<2i^a}NF zZ_G$cK$QRS3+azOjNDhQhe!DOG#9TVSn5m=!9^`!i+Vof@EI^({j2hfnPvr20j7eN zlU2yW!(qQ*Vl$tlHz545t=2X3YcQQ|y>)wQrt;q6THgh6oigI_ zzC(*>%I2nhybKT`@(DkxY;7rq*g6lfNGP4Fza+h&pbZG#TrJ}H=pVa+>=U+{lTQGP zdYXyzhVUGwh;!%N)!M;ezX)J$y<@UWH*8R8U;deqc z8?cK-gNfbk*M3v(o>h{TGytAq9Jw3JgLWSBT`2)q)^?Fp@s>93ArYm6gq2J;x95Is zvNiKBU&>|bJSBClz^NRSG%5MR)f}pD_US~b4Y?+5;j7>%wo)}x`EUOkurMEI*QIhARy(^2%?a?So}TiYn{QWtuoDtN)^5ON9Bj- z1Y$p*fL|;Tg`L`Kk%zgChf0OcRHi39n2)G)6<`VuN;8P*HiPZS;nH`e&mV)h9)4;m zO-%TrZs?^Qf8`kkIMu?-X+0A7Q?Mv2T`}f@Jp$EekJ=W_^$gPB{fj2QM5V(nb%y4P z`Hc0*ul^v`UHEL2%%*Q{WzS88v#0%P`O~<44z{jxidK;RcIHd~vw6{#>QI5U=zMryP+fc?r2n-e z_9*jvqW7|hw{q`}xdZcp;p>DK8td20L*tX$E-t1%#PsC`^Z=W~nO<&U9yIJTlDc-6 zNnWtIc#Q1RCOdDGRFG;S!sl7oHy78SJS&Biqsg9`u3NPneRs;Vk=Az=WC+@uAvP`XgR!919-D>_nb7!&y3!9u+T#5-?r*>BTR&>xuPmBO zlG9fWy?eT?C81MCY7ZlMdF}c9Nvq?g!jxo4oowgYDeuNpewlC`&aB{)+kPZ8A54W9 zEh#OVTBx#Ud{lazPcQi14`4IqpF0L|Ea*tTvil>KYGirHLpBX|`nplhEiPf~&OiRA zEm2094E%-)QuIC>)qj?ZrUI23;4GnOaA`t6}_3Y2FcQ{ISbrU9#gjF3*z1=*wAqnz*l3 zce{>fWisolVCOtHgYYCog^f02|8#r&YO{13nyy7C^l@F{fMG3^RyDg!mxnA)I9@T{ ziv-*d?C2@gdW)yOe)4@cu2Ery7<7bEX1Gw%m%z$pX_C7t+!|eB$W^&$9HpGza$7*AliezCU~yBF~#zUz!RBQk41aLp(enOg}LB z@ngGk8Y?6^XT9uWEUuClSdY4%(1ZM19V&~>!Ifh+k-6~p7QB{Ik>uh7fBtZ?mUq*& z$mP(*YJ!Dr>$x$rsXx|t34FN=TjSg1Z_mi+{rD8b7penfg`OBJU42~P85`LY1AYG~ zw6F19o)zm@zQl3PDzbBlm1sVGrMS!}^GSbu6GbCp!S2?YQdf}W2USyldBO->*xs)9 zoT^24$KJRD9kD2m?JgE9Ep(flRA4+(XMG}g<p)gwMx@Ai^o63zBNh>kb038);Q%eV@Z8mB-jr!yA|(G z@J(34_-=KjMM>8PC_Yj_Cijr6YU}R$f*xC*BTizMi{@EYCuw42Jo}5MrZg{#Zx3#7 z&dc9Xv(F*xV9r+&te)z^EniJ0@{c!EQJ6CD#R8aq6-MtOay5vDPx}xT=24R_)ATYZ zgS2NqEO$aI8cxdhQo&`BGO|0+9^J-|XrjTHvXD7zG^djsovM~Q`WzIiDVd`!#h%S; zge>ynt+H|Z_%#n4R6+6h>;*X!k19}AULlFK2e8@9ie;#?Amv^MP$^w*W_h4;@-tL< zWnZE#0Q9Uv-MLOMH2M(a+d5^xvT%3Gx>=DcsdjpA7^m@%Lh{maEY?F_Iz~9Bd|Qwg zA5{3a<;qlOSmAtF?3h-pwDc>N1-4EX#L8T}Q`7Fgo2H8Rd$YCs%@o82%NYo}d*;CM zc!Qayr}mqM#JeKi{orM>Sl}!sO74X$?0pRJJ|XV54i(KDb)LQ)9}&d9D8J*z1(Mt1 zck$~AI{iM9wKfsX~sjnR!XJ2Yyt*gK6_co3O9|OCP9Be=U<~f{i9g^0l^p^ z#w>A`I^tIdU@Q3Z-GHYI-nRNW3^(U>`>B(By&G4%^$Q#!R`52FP=A*0!|B@gOriSk zF3rCg8w_XDHW>Z%Duec{AI$ATe$9r(?v#kuI5PZPnG!n>2Aa1@&*E7nxjmVxur6s~ z;ziTb0$9$%yIkoXg;9Pp zZ0f6$+rM^Q+3xku+a5J#lsp=*}38npJ&94yC_fcnH`T z!orsvBtL~AR#vSfA|WccgIn5R}`c?=j@pUwOmTOI| zyP!<$VD0w_pH+uy#2bP2V;3`VjalnpGRHckDxJvbjzi)Xa*^~zi6|Dz=OwxtFE~Ta z*&_*~MZ_P3sZF0~NBB>#YhGWf2fswF13rhg*apbjd<@BlGWj7_vYuapgN>pMAiV4?(^(eY+~zwK`p>Um)^eQ3bf0rqOPv8-e5pcc7TCK z+w>hz5OtVr7lkI<<^zdelP7S-X}qo8#JvUE{osTxByJ8Qv_=4>tvHf)&Tk%@Zk?+i zY7RZ~Y)HVBhe3<~Cf_?#G`UyNyA-q@Fu!M9D0W&>^nd9H|I-ux`vx!ww{|7#(6L~Q;b05 zNP4d48E3lUOK;6hLzjvez97Th%6+z%B{W0Fm!Ni%`s<*K2NdIG|Ffb~I?dAgxUxRX zoBN|}Of_0_FHGB8;)y|L@9fJ5Yk98I_6t@El0%;o*yBF(FppAltCEy1ObwC)^`?-e zUNkN=yXe*>ff*nX&RfNr;^VFBg7e>wPgYw}$s=CH$XW<_S?ha0%yK88c%dAESfUJl z)gp*xe0Y3WgT~_!(K@xe5gj`cx9>Q?F7%1Q$uQQO#v6TWv!#R5Tz_gWlq|tAAUbsG zP)dD(FJ@M?*auO}bN}#!yPLCEMSl+=)xOU?w|`n7GL^qUY|q!``S^x{bbq|kwtbRp zcKiE6SosnLrT2}tzwS|d&`r%D(uVwZdDH=UcU#^GhA4RFPlGU~jPl-pnok#&#+Rh)vilvF<#3{6{-S^rFtme}qCLP3w zHo;+7tA?6)V>Ur^9FAgCzNM}yt@giG}KLoW9|xhgl=h{+gxx1Q8xd*w?LqjWUj5hbgHiZMVj zgsz5!L|RJ0hrP0Mz=Aj8NMa973vV-pd!U&V(^q5mp_E-A*l|s+`MBYny!L3-L%oP{qZ5`8HJ&Ab^>iF1ga#x3%lQXH> z{`ttfp(YMb`g+s!#v)ruKBXyM|5f?h|5 zAmu#TK_;x9ms5b52; z%i}~Ilzj8Z;dQaIyMl==6S$>L$;-r>tB_waWsVIHg-a^T6ws4Cj+G_0-1H~Sf2vGo zn6<+*W_k#~qRs4btoj)L7P06b1$1Y%fY4$VW=&Auc{u~qr}_B8a@HVo;bnI7mQnd= zRDIy;=j?^o1GwjQr?ph{}Fr7s!O0P~3<%{}OMg*)??>?kRy2dNM{r2T zwXpt0!NLJPfir_Q27-j0_9AyP za>BtZ448@Z(S+EMyO;OP9q#ZkU#uaCYKDCj;w#N`e8j zYCXpJ3;5HTAjBaRpGB|-Rs*i1JrOgPkv+20_018 zy6Bi_U6i5y&Du2KBoXk^o+-!=VU)VPK0Wi>6Nmv!muZBOr3Sw#zl3zOTrz189DFcrA^_%E7% zjd}=w)Dr{de-tpW6I3a{fErzmvPg0?>3YoKk)HJ6NPG-Liz)CPk(XyVk_i2C?I5x0zPnZ%=mDv#JyL!T$wgyGEY((QGB!`JCKo{ zZsfFF8?RBGbhLbD80QMb5^m!;ui{H?U*?(nlvKy1n0bMyE|m92Co{lqbf%L8;>2?E z#<3tBzd?>45tXg>QD%D7MXkZm3o}1kLn8bZ2M*ndnAYhE=N!LD9{gxC=L_W+c%~!7 z(>?NJ4t9o0mN&i>s)fl-w)K?ZdC2d0L4xf&D7dC=ax1~BmCTgLe%ZHkS%r0%U9!X zSuK4eV6C!8J&4jPlWsfj)KjBzin5Sd8RcWp^@+DE1DRg>20?!`xIvL3UazgLRYmO4 z9OYJIJ|%_Ue^iu2A^v>TVWEilgwurMX_0wts&lJE=JdXORYfJ8b2ZzP5y7P}zkS27 zjbX$Aw%QyBn7s~v66G=b%{?F1yaLCsb8!+{EnEe#^Om1)Hr4#2Kqn0Pyt&Id%ns)S zZX63~fbfHtf5IyZ_W6L9=pq{DgGXY@v+y}NwzhLtu-G}LTSwDQ_H7*L>lEzHBQ^xP z*cwYsCKaU;@V&e|0tD52T%`JFvq0ZQ7z5I(h3sv&g|>{Pp715u!)Vvu+8VfW#M2U} zJ#(nPQGUBZ5xmS>C{>VS(=zt504}yM3>H84EnX=+hOFE#H_FpXWm`ggn)p=lFh8Iu z!b%REo;%Z+IoP^_raw7kAl}3y)`IA*epK*L(wn*dqgW`B8Ja*=c9?FSsO~u^a9jDU z{{8uASuA;$_I*l)R^s281;mCm)a%vbUCuZLJN2KeYk5=uxV<;rD^YD?L^g7xn)~WZ zBVSdIm=iqCz`|Ylgsvt7#pr>Yy{z&cq8uq*)i5Mq%YWso3&2Zs>$aBXo7draqH@cX z(dsBT$>B1#1NW7b0e9CF{ff!n0JhDaM_j3M?W&Mf^)~v(;)l1~0`K1@gpz+RRZX0u z@hPQYh{usoj_Nwr4qoR-`nP_WNSXbv@bD3oy=O`(B6`2`NOsjQ?=yjQY{#xiW3^h` z;O~sLy7?RtQMtTCk*9cCauGU$QU8wLB0MJQ;>U`OSHXOXPIM4x0*{4^EjQ%@9P0C) z*kq`&%5yTi&f@vgU+yBvGc5qkeu&e;!awYN?FO^D@wy?|=FS z%pBAiGfguGl_y&3yd2>Ep7Ag^5Qwn@CIk_I;VR&E_*}4Yl!JIi=?PMBXT}bj zGj{Lxkg4%6q7k{U5CiY~TA*80fI54P-(UT-*nOo=Re)5vY-QH{MLoy=g$DOXst^Ox zA-~uGe4)N|ZuV>$I>&*l7ZnEs5GC{-K3&kFnjU1VI19nuj!s{%6$TE;A;*AfPBBO0>0W)lFYU{ zO;P`rj;eN8V#O!CjlbVW0e)wydrN*_B6CqNb%X-jnej#PABCcC9`xhD(Ji&Nc31=w zImWR7BL(VR`>{Lh^#&{^k*V%UQ0FWRl^=;65$dzp+T|j$?UL!AdJ`yNUm?}l;Uxc6 z7CKmxv$TiTjc&}m=-Q@qFQHilW!#Sb!9+u5zDjym?e*Mo>Q0kh{vPDq`#Fd?#3%k| z_hJSI@kx*H!ClRxlp#o>R~JYEVVZaf+lxx|P?qVyQWt7|BG1VizlV>*GOVioX~rk4h8f$tU5Dolcd5x%Tb0RB0L_bp zmoKZSuR9yH1J{5FyQNMxfTamZXbVVaLfV{^%5~hJ*0xI}3(tp14EI12#ML)h1?p6j zOLayZbY!#eHuI=ixBg!!(?3q;M^DJ$it~(dKQ0d?&QY;QGpy(4l8k!t1hFm968+|Z~21OE#rue_!Ph8A!T@_ z3tdQ^${@0RLC5*bZ^xmz(DSK>&EtmD4k#^I=v(bC0KgjGBCjqyDf$dR^rIdqAQ~h_ z2BCHz%a`7CEX!E~X!Ma62(>FH9SN7M0KE5!Q%r!I z>bQEy(69$*vwJpZY46Y%7n1Sh-sE&>^pUN^rYP$Lik-6ksTMnBR-Tr# zLV+)H;q7-bx#nqFh8x0SjBR%tV-p{)yXUE&OJ*nQmVRr960%PBc*a5<|BX!snyKP9 zS*as%JMdZ;g~Ih zJM-U8%PeAVQT?0x&myx;6k0VjZELwT)?}Mi(G!w4-x1_9m^E%d^+EWiG4++FZEI7p zTep6&Jj7>VsX8H?>eqLd*hB3_{5%(h$9?<_wi2jyr?`@QNd2k3TvN>d&I;%1Ij-xQllr>J-GjePO>aoRJd*@okFY z5nP_G*f%==2Tqd~Lq9gSq<$Hb?f+Y)Ztm>W&SH(2hbUq0Bm5{96{E}|nGTUk9N9=lU zR&b>7ro)naSWoA8&h&Xm!v_Bh8T%TfGaHW3S&Rc^4 z@u9i!$Q!N8v{P@^Bzru4KART1IwNIzM8chF4@{E;<&I5FraPmZwg%UWL#NdjQ>*R_ znn*khcTwgK_~~=hSo5}dL^T{4>FSUfgMJS6v+ClOT~j}AB^IYNj#8=5zfuXd{MlTh zzi4&di1ZxsmTel7oxSwm4nxwIVx;k*AkR;{lY*dKU+oxj$4-9ft$E)uSL6;ve_)G;DyDwd_HB!{mfi78&oXCO#qRdp4#W6qDf!4`#VyfU# zt>B|6hD~%o$9TjE_iDYKo&OKT!m!QvPC_F_6pOM$e~z0(~R=3JPA6vh7JY=G8>Rs+wJ74hK2B|fecrOG~>OmThzKA&vll(_yHWta*Af-7g3n*M`wOw0Wp>khzmuh zws7%zbjaB(T&3d+$;_-i&1kkd($LIW*?0o|IiA20D<~LYf?4Qj2gBXg`nFOn)~DKT z!1qD13cW-1&f?tg>R8hCiT(koaPL2g#*eOVw=aisU>4h%LRM4Pe^aij4P0NlF23Q1 z2z6@qVeKUFa|&}#&7`jf2Sw^gXp3!2As8<#Vti89Xm{H=)}gx)8RBz%Jqh7*5%&I# zoxTD;BQ{Y2jW5Sa8)7n3HoALnb~_8mCCUPu#o-*7;jgZKs|(gmdPUdDmteiUG=v|4 z7gL*yS79FXbs1Pr=)st!M=+nWY*U|UK{MhyR`gffcb|#(72hgrdDecFS@3Kc{v4=O zlRHW|5!l`PZrpL9fc9SZtQioo{~Y>0OvN%5U%Xs{&T5@SMQcK=5nnyHlYwi8`JG$N zVU5{Vq4+rPI~ASZJ6Tcsbd^KiNAz|4C{(_T&W4Q_V<5D|Pw4gb8*EhoRmAA8{m;dz zFNB79xE-eibDF(6f_J1Que11K%Iq$i71_9qzrE-q&;qKB9oiE;?Xx=e2=jg2K?3uh zUm>DUNwy@6CRJew3vwl!6a3s+h^*Jh4Dzm`U&|h=Q-nUNcfMZt=^k`Qg7w(PJMAcH z?hUWu0HT48gBWsWNN#xpI{!@l{(9zgaRr05z1#EXFF#)0HM0CTqof9Igy>^pp;aP5 zoqfywBp`E{0B?JUb}p_bYAqXz;E&s2BbuM3tKT=^$?BKX)Ca?i_`UshS}&hIM^;|=2drcvlTZ)4eZ%aU&sc?%xSBi*i5 zvJ7b+U|~Wg#OSbLv)H`Xqs*~} z1PBew(a0+8xa=j*;DG2n{2$@-Qa+>98<|g77=-GR?>T@QjH}_7YaH&@5Ih%^yp+cd z)aqp$YK;VfosRc;a4w{VA~FQc^Tl*DI~RlSamAfS7^7`G z%Y4*7g-gTB2Q3p;2ZvN;&VN5>br*E4dsCx3%jXMhKS4;a4l_8WFb##(EIdYI8IR|I zm+}3eZ3Wr+{4Q2;@8%kVQi|2+djNLStv5@>ly9=pAbRMz{+pDgzIeWag za{Wl|_%-SvMGM#pSY84u&s|`coZgLHx&OVBl(D!fTz-)+8ZXUFX7ifbM=z%=RZew! zO`jS^KtLz9`vNIWwz@URgA4U;b=v&z!uJjBd@oNMIOjen6v(!KQYS%dVTUP@9h4%?R zy)%;DJ1Tes%K={7m^uMRS=3^GnH1-*EsbWe!bUc<$5_TVNZGq+j23#fX23rKZ*go$ z2imcy{n(&Su^#kd=PcN#@4`|JOk`uheXqNO1HEhb#>6X=EDL=RI?ot??MCE0?9d@n z@LJzy^ZewMyV&TvaT{sY!uYMkp6@s$0nny4 zYPS1;r3!FQ&@T4VO$1smIJEL#>mIw#Od$ZL$L%YUX(0qV+h3Vry5}Jx+(%1hF0w~K zlDNPHq?5|){ijkKy*YW0C$Bfz4BL=~fh$J-!b7mm77d))jP8ff$WX?8kuVvx-LZS1 znYdNd2h%<1fUw+YG!6IZ9bNv{f_M&6H{Od(8-y*^XVqD=_v;A07j6w#ygx|uTbP5* znJ@!6(+W%nVGLPj5GxAt$3!1eR+X1xOz6;y8q7y7Lcf%Vb3FE~B_ldwdj}$*3;-WJ z@ci<`2W#}Rju^XWtj-DoF9swp_OGt(sgbQR2dOQWdC6)Qosgm}<*?e2?Fn_d`+YIZa%A7Jq0Q z|8*9lrnj*e1S`A^3cRk#OO!+7OBDsJwuq~{SE<Z*I>p4QQqT%6!$Tzb=5WECz-y${D{NQ%DH7kt zka>x{_#vPjS0c{}%8ZHrQKx|!KfpWYG&z{WJZP+o8_#(kBgJAozczh>>H;I+wbA?L zgh$3AXaRK~2>Za^isoy7+y?gp6IQkphTf+qW%0(bN`GJCVB%(&-2sbc&_kI_TFcJ< z^Y1~A$#vnvJ4lQUa)yKHaQ`OO3O{GY=x89RIF!PO4i>c)65|#Y^q{KWg=8YH!IxL8 zh^*fJD-gWcQacIUrs6*{f{UCsRu7y(oXIK}JXwVjpW;#M-v`c9-2W-Wj+gbR<_w=9U zi6cf<&OtfrnO9xvLzOo;3XTP)_Js+KL>MWj`aIN<@Bmh{W?k>0Zbe6TDLIHW*YRc6 zOBlz|96rKqa>G!%BVb5YRp;nn3)I%wB7wUG3`H@zODv(c%4@NK`ybXCK}@!hNd*

cSXz$7_=e;7K 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 literal 0 HcmV?d00001 diff --git a/Tests/TUIkitTests/ImageTests.swift b/Tests/TUIkitTests/ImageTests.swift new file mode 100644 index 00000000..fb019180 --- /dev/null +++ b/Tests/TUIkitTests/ImageTests.swift @@ -0,0 +1,337 @@ +// 🖥️ TUIKit — Terminal UI Kit for Swift +// ImageTests.swift +// +// Created by LAYERED.work +// License: MIT + +import Testing +@testable import TUIkit + +// MARK: - RGBA Tests + +@Suite("RGBA Pixel Tests") +struct RGBAPixelTests { + + @Test("RGBA initializes with correct values") + func rgbaInit() { + let pixel = RGBA(r: 255, g: 128, b: 0, a: 200) + #expect(pixel.r == 255) + #expect(pixel.g == 128) + #expect(pixel.b == 0) + #expect(pixel.a == 200) + } + + @Test("RGBA default alpha is 255 (opaque)") + func rgbaDefaultAlpha() { + let pixel = RGBA(r: 100, g: 100, b: 100) + #expect(pixel.a == 255) + } + + @Test("Luminance calculation follows ITU-R BT.601") + func luminanceCalculation() { + // Pure white + let white = RGBA(r: 255, g: 255, b: 255) + #expect(white.luminance > 254.0) + + // Pure black + let black = RGBA(r: 0, g: 0, b: 0) + #expect(black.luminance == 0.0) + + // Green contributes most to luminance + let green = RGBA(r: 0, g: 255, b: 0) + let red = RGBA(r: 255, g: 0, b: 0) + #expect(green.luminance > red.luminance) + } + + @Test("RGBA equality works correctly") + func rgbaEquality() { + let a = RGBA(r: 10, g: 20, b: 30, a: 40) + let b = RGBA(r: 10, g: 20, b: 30, a: 40) + let c = RGBA(r: 10, g: 20, b: 31, a: 40) + #expect(a == b) + #expect(a != c) + } +} + +// MARK: - RGBAImage Tests + +@Suite("RGBAImage Tests") +struct RGBAImageTests { + + @Test("Image stores correct dimensions") + func imageDimensions() { + let pixels = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: 12) + let image = RGBAImage(width: 4, height: 3, pixels: pixels) + #expect(image.width == 4) + #expect(image.height == 3) + } + + @Test("Pixel access returns correct values") + func pixelAccess() { + var pixels = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: 4) + pixels[3] = RGBA(r: 255, g: 0, b: 0) // (1, 1) in a 2x2 image + let image = RGBAImage(width: 2, height: 2, pixels: pixels) + + let topLeft = image.pixel(at: 0, 0) + #expect(topLeft.r == 0) + + let bottomRight = image.pixel(at: 1, 1) + #expect(bottomRight.r == 255) + } + + @Test("Set pixel modifies correct position") + func setPixel() { + let pixels = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: 4) + var image = RGBAImage(width: 2, height: 2, pixels: pixels) + + image.setPixel(at: 1, 0, value: RGBA(r: 128, g: 64, b: 32)) + let pixel = image.pixel(at: 1, 0) + #expect(pixel.r == 128) + #expect(pixel.g == 64) + #expect(pixel.b == 32) + } + + @Test("Add error clamps to valid range") + func addErrorClamping() { + let pixels = [RGBA(r: 250, g: 5, b: 128)] + var image = RGBAImage(width: 1, height: 1, pixels: pixels) + + // Adding positive error should clamp at 255 + image.addError(at: 0, 0, rError: 20.0, gError: -10.0, bError: 0.0) + let pixel = image.pixel(at: 0, 0) + #expect(pixel.r == 255) // 250 + 20 -> clamped to 255 + #expect(pixel.g == 0) // 5 - 10 -> clamped to 0 + #expect(pixel.b == 128) // unchanged + } + + @Test("Nearest-neighbor scaling produces correct dimensions") + func nearestNeighborScaling() { + let pixels = [RGBA](repeating: RGBA(r: 128, g: 128, b: 128), count: 100) + let image = RGBAImage(width: 10, height: 10, pixels: pixels) + + let scaled = image.scaled(to: 5, 5) + #expect(scaled.width == 5) + #expect(scaled.height == 5) + } + + @Test("Bilinear scaling produces correct dimensions") + func bilinearScaling() { + let pixels = [RGBA](repeating: RGBA(r: 128, g: 128, b: 128), count: 100) + let image = RGBAImage(width: 10, height: 10, pixels: pixels) + + let scaled = image.scaledBilinear(to: 20, 20) + #expect(scaled.width == 20) + #expect(scaled.height == 20) + } + + @Test("Scaling to zero returns empty image") + func scalingToZero() { + let pixels = [RGBA](repeating: RGBA(r: 0, g: 0, b: 0), count: 4) + let image = RGBAImage(width: 2, height: 2, pixels: pixels) + + let scaled = image.scaled(to: 0, 0) + #expect(scaled.width == 0) + #expect(scaled.height == 0) + } +} + +// MARK: - ASCIIConverter Tests + +@Suite("ASCIIConverter Tests") +struct ASCIIConverterTests { + + @Test("Target size calculation preserves aspect ratio") + func targetSizeAspectRatio() { + let size = ASCIIConverter.targetSize( + imageWidth: 100, + imageHeight: 100, + maxWidth: 50 + ) + // 100x100 image -> 50 chars wide, ~25 chars tall (2:1 aspect correction) + #expect(size.width == 50) + #expect(size.height == 25) + } + + @Test("Target size respects max height") + func targetSizeMaxHeight() { + let size = ASCIIConverter.targetSize( + imageWidth: 100, + imageHeight: 200, + maxWidth: 80, + maxHeight: 20 + ) + #expect(size.height <= 20) + #expect(size.width > 0) + } + + @Test("Target size is at least 1x1") + func targetSizeMinimum() { + let size = ASCIIConverter.targetSize( + imageWidth: 1, + imageHeight: 1, + maxWidth: 1 + ) + #expect(size.width >= 1) + #expect(size.height >= 1) + } + + @Test("ASCII character set conversion produces output") + func asciiConversion() { + let pixels = [RGBA](repeating: RGBA(r: 128, g: 128, b: 128), count: 100) + let image = RGBAImage(width: 10, height: 10, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .ascii, colorMode: .mono, dithering: .none) + let lines = converter.convert(image, width: 10, height: 5) + + #expect(lines.count == 5) + #expect(!lines[0].isEmpty) + } + + @Test("Block character set conversion produces output") + func blockConversion() { + let pixels = [RGBA](repeating: RGBA(r: 200, g: 100, b: 50), count: 100) + let image = RGBAImage(width: 10, height: 10, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .blocks, colorMode: .trueColor, dithering: .none) + let lines = converter.convert(image, width: 10, height: 5) + + #expect(lines.count == 5) + } + + @Test("Braille conversion produces output") + func brailleConversion() { + let pixels = [RGBA](repeating: RGBA(r: 255, g: 255, b: 255), count: 400) + let image = RGBAImage(width: 20, height: 20, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .braille, colorMode: .trueColor, dithering: .none) + let lines = converter.convert(image, width: 10, height: 5) + + #expect(lines.count == 5) + } + + @Test("True color output contains ANSI RGB codes") + func trueColorOutput() { + let pixels = [RGBA(r: 255, g: 0, b: 0)] + let image = RGBAImage(width: 1, height: 1, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .ascii, colorMode: .trueColor, dithering: .none) + let lines = converter.convert(image, width: 1, height: 1) + + #expect(lines.count == 1) + // Should contain 38;2; (foreground true color escape) + #expect(lines[0].contains("38;2;")) + } + + @Test("Mono output contains no ANSI codes") + func monoOutput() { + let pixels = [RGBA(r: 128, g: 128, b: 128)] + let image = RGBAImage(width: 1, height: 1, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .ascii, colorMode: .mono, dithering: .none) + let lines = converter.convert(image, width: 1, height: 1) + + #expect(lines.count == 1) + // Mono should not contain color escape sequences + #expect(!lines[0].contains("38;2;")) + #expect(!lines[0].contains("38;5;")) + } + + @Test("Floyd-Steinberg dithering does not crash") + func ditheringNoCrash() { + var pixels = [RGBA]() + for i in 0..<100 { + let r = UInt8(clamping: i * 2) + let g = UInt8(clamping: i) + let b = UInt8(clamping: 255 - i * 2) + pixels.append(RGBA(r: r, g: g, b: b)) + } + let image = RGBAImage(width: 10, height: 10, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .blocks, colorMode: .ansi256, dithering: .floydSteinberg) + let lines = converter.convert(image, width: 10, height: 5) + + #expect(lines.count == 5) + } + + @Test("Empty image returns empty lines") + func emptyImageConversion() { + let image = RGBAImage(width: 0, height: 0, pixels: []) + let converter = ASCIIConverter() + let lines = converter.convert(image, width: 10, height: 5) + #expect(lines.isEmpty) + } + + @Test("ANSI 256 output contains palette codes") + func ansi256Output() { + let pixels = [RGBA(r: 255, g: 0, b: 0)] + let image = RGBAImage(width: 1, height: 1, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .ascii, colorMode: .ansi256, dithering: .none) + let lines = converter.convert(image, width: 1, height: 1) + + #expect(lines.count == 1) + // Should contain 38;5; (256-color escape) + #expect(lines[0].contains("38;5;")) + } + + @Test("Grayscale output contains palette codes") + func grayscaleOutput() { + let pixels = [RGBA(r: 128, g: 128, b: 128)] + let image = RGBAImage(width: 1, height: 1, pixels: pixels) + + let converter = ASCIIConverter(characterSet: .ascii, colorMode: .grayscale, dithering: .none) + let lines = converter.convert(image, width: 1, height: 1) + + #expect(lines.count == 1) + #expect(lines[0].contains("38;5;")) + } +} + +// MARK: - Image View Tests + +@Suite("Image View Tests") +@MainActor +struct ImageViewTests { + + @Test("Image initializes with file source") + func imageFileInit() { + let image = Image(.file("/path/to/image.png")) + #expect(image.source == .file("/path/to/image.png")) + } + + @Test("Image initializes with URL source") + func imageURLInit() { + let image = Image(.url("https://example.com/image.png")) + #expect(image.source == .url("https://example.com/image.png")) + } + + @Test("ImageSource equality works") + func imageSourceEquality() { + let a = ImageSource.file("/path/a.png") + let b = ImageSource.file("/path/a.png") + let c = ImageSource.url("https://example.com") + #expect(a == b) + #expect(a != c) + } +} + +// MARK: - ImageLoadError Tests + +@Suite("ImageLoadError Tests") +struct ImageLoadErrorTests { + + @Test("Error descriptions are informative") + func errorDescriptions() { + let fileError = ImageLoadError.fileNotFound("/missing.png") + #expect(fileError.description.contains("/missing.png")) + + let formatError = ImageLoadError.unsupportedFormat("bmp") + #expect(formatError.description.contains("bmp")) + + let decodeError = ImageLoadError.decodingFailed("corrupt data") + #expect(decodeError.description.contains("corrupt data")) + + let downloadError = ImageLoadError.downloadFailed("timeout") + #expect(downloadError.description.contains("timeout")) + } +}