/********************************************************************//*
 *
 *   Script File: zupply.cpp
 *
 *   Description:
 *
 *   Implementations of core modules
 *
 *
 *   Author: Joshua Zhang
 *   DateTime since: June-2015
 *
 *   Copyright (c) <2015> <JOSHUA Z. ZHANG>
 *
 *	 Open source according to MIT License.
 *
 *   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.
 *
 ***************************************************************************/

// Unless you are very confident, don't set either OS flag
#if defined(ZUPPLY_OS_UNIX) && defined(ZUPPLY_OS_WINDOWS)
#error Both Unix and Windows flags are set, which is not allowed!
#elif defined(ZUPPLY_OS_UNIX)
#pragma message Using defined Unix flag
#elif defined(ZUPPLY_OS_WINDOWS)
#pragma message Using defined Windows flag
#else
#if defined(unix)        || defined(__unix)      || defined(__unix__) \
	|| defined(linux) || defined(__linux) || defined(__linux__) \
	|| defined(sun) || defined(__sun) \
	|| defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \
	|| defined(__FreeBSD__) || defined (__DragonFly__) \
	|| defined(sgi) || defined(__sgi) \
	|| (defined(__MACOSX__) || defined(__APPLE__)) \
	|| defined(__CYGWIN__) || defined(__MINGW32__)
#define ZUPPLY_OS_UNIX	1	//!< Unix like OS(POSIX compliant)
#undef ZUPPLY_OS_WINDOWS
#elif defined(_MSC_VER) || defined(WIN32)  || defined(_WIN32) || defined(__WIN32__) \
	|| defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
#define ZUPPLY_OS_WINDOWS	1	//!< Microsoft Windows
#undef ZUPPLY_OS_UNIX
#else
#error Unable to support this unknown OS.
#endif
#endif

#if ZUPPLY_OS_WINDOWS
#if _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NONSTDC_NO_DEPRECATE
#endif
#include <windows.h>
#include <direct.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <io.h>
#elif ZUPPLY_OS_UNIX
#include <unistd.h>	/* POSIX flags */
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <ftw.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#endif

#include "zupply.hpp"

#include <chrono>
#include <iomanip>
#include <ctime>
#include <sstream>
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
#include <functional>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <deque>
#include <cstdarg>
#include <csignal>

// UTF8CPP
#include <stdexcept>
#include <iterator>

///'k': local variable is initialized but not referenced
#pragma warning(disable : 4189)

#pragma warning(disable : 4244)

// unreachable code
#pragma warning(disable : 4702)

namespace zz
{
	// \cond
	// This namespace scopes all thirdparty libraries
	namespace thirdparty
	{
		namespace utf8
		{
			// The typedefs for 8-bit, 16-bit and 32-bit unsigned integers
			// You may need to change them to match your system.
			// These typedefs have the same names as ones from cstdint, or boost/cstdint
			typedef unsigned char   uint8_t;
			typedef unsigned short  uint16_t;
			typedef unsigned int    uint32_t;

			// Helper code - not intended to be directly called by the library users. May be changed at any time
			namespace internal
			{
				// Unicode constants
				// Leading (high) surrogates: 0xd800 - 0xdbff
				// Trailing (low) surrogates: 0xdc00 - 0xdfff
				const uint16_t LEAD_SURROGATE_MIN = 0xd800u;
				const uint16_t LEAD_SURROGATE_MAX = 0xdbffu;
				const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u;
				const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu;
				const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10);
				const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN;

				// Maximum valid value for a Unicode code point
				const uint32_t CODE_POINT_MAX = 0x0010ffffu;

				template<typename octet_type>
				inline uint8_t mask8(octet_type oc)
				{
					return static_cast<uint8_t>(0xff & oc);
				}
				template<typename u16_type>
				inline uint16_t mask16(u16_type oc)
				{
					return static_cast<uint16_t>(0xffff & oc);
				}
				template<typename octet_type>
				inline bool is_trail(octet_type oc)
				{
					return ((mask8(oc) >> 6) == 0x2);
				}

				template <typename u16>
				inline bool is_surrogate(u16 cp)
				{
					return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
				}

				template <typename u32>
				inline bool is_code_point_valid(u32 cp)
				{
					return (cp <= CODE_POINT_MAX && !is_surrogate(cp) && cp != 0xfffe && cp != 0xffff);
				}

				template <typename octet_iterator>
				inline typename std::iterator_traits<octet_iterator>::difference_type
					sequence_length(octet_iterator lead_it)
				{
						uint8_t lead = mask8(*lead_it);
						if (lead < 0x80)
							return 1;
						else if ((lead >> 5) == 0x6)
							return 2;
						else if ((lead >> 4) == 0xe)
							return 3;
						else if ((lead >> 3) == 0x1e)
							return 4;
						else
							return 0;
					}

				enum utf_error { OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT };

				template <typename octet_iterator>
				utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t* code_point)
				{
					uint32_t cp = mask8(*it);
					// Check the lead octet
					typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type;
					octet_difference_type length = sequence_length(it);

					// "Shortcut" for ASCII characters
					if (length == 1) {
						if (end - it > 0) {
							if (code_point)
								*code_point = cp;
							++it;
							return OK;
						}
						else
							return NOT_ENOUGH_ROOM;
					}

					// Do we have enough memory?
					if (std::distance(it, end) < length)
						return NOT_ENOUGH_ROOM;

					// Check trail octets and calculate the code point
					switch (length) {
					case 0:
						return INVALID_LEAD;
						break;
					case 2:
						if (is_trail(*(++it))) {
							cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
						}
						else {
							--it;
							return INCOMPLETE_SEQUENCE;
						}
						break;
					case 3:
						if (is_trail(*(++it))) {
							cp = ((cp << 12) & 0xffff) + ((mask8(*it) << 6) & 0xfff);
							if (is_trail(*(++it))) {
								cp += (*it) & 0x3f;
							}
							else {
								std::advance(it, -2);
								return INCOMPLETE_SEQUENCE;
							}
						}
						else {
							--it;
							return INCOMPLETE_SEQUENCE;
						}
						break;
					case 4:
						if (is_trail(*(++it))) {
							cp = ((cp << 18) & 0x1fffff) + ((mask8(*it) << 12) & 0x3ffff);
							if (is_trail(*(++it))) {
								cp += (mask8(*it) << 6) & 0xfff;
								if (is_trail(*(++it))) {
									cp += (*it) & 0x3f;
								}
								else {
									std::advance(it, -3);
									return INCOMPLETE_SEQUENCE;
								}
							}
							else {
								std::advance(it, -2);
								return INCOMPLETE_SEQUENCE;
							}
						}
						else {
							--it;
							return INCOMPLETE_SEQUENCE;
						}
						break;
					}
					// Is the code point valid?
					if (!is_code_point_valid(cp)) {
						for (octet_difference_type i = 0; i < length - 1; ++i)
							--it;
						return INVALID_CODE_POINT;
					}

					if (code_point)
						*code_point = cp;

					if (cp < 0x80) {
						if (length != 1) {
							std::advance(it, -(length - 1));
							return OVERLONG_SEQUENCE;
						}
					}
					else if (cp < 0x800) {
						if (length != 2) {
							std::advance(it, -(length - 1));
							return OVERLONG_SEQUENCE;
						}
					}
					else if (cp < 0x10000) {
						if (length != 3) {
							std::advance(it, -(length - 1));
							return OVERLONG_SEQUENCE;
						}
					}

					++it;
					return OK;
				}

				template <typename octet_iterator>
				inline utf_error validate_next(octet_iterator& it, octet_iterator end) {
					return validate_next(it, end, 0);
				}

			} // namespace internal

			/// The library API - functions intended to be called by the users

			// Byte order mark
			const uint8_t bom[] = { 0xef, 0xbb, 0xbf };

			template <typename octet_iterator>
			octet_iterator find_invalid(octet_iterator start, octet_iterator end)
			{
				octet_iterator result = start;
				while (result != end) {
					internal::utf_error err_code = internal::validate_next(result, end);
					if (err_code != internal::OK)
						return result;
				}
				return result;
			}

			template <typename octet_iterator>
			inline bool is_valid(octet_iterator start, octet_iterator end)
			{
				return (find_invalid(start, end) == end);
			}

			template <typename octet_iterator>
			inline bool is_bom(octet_iterator it)
			{
				return (
					(internal::mask8(*it++)) == bom[0] &&
					(internal::mask8(*it++)) == bom[1] &&
					(internal::mask8(*it)) == bom[2]
					);
			}

			// Exceptions that may be thrown from the library functions.
			class invalid_code_point : public std::exception {
				uint32_t cp;
			public:
				invalid_code_point(uint32_t _cp) : cp(_cp) {}
				virtual const char* what() const throw() { return "Invalid code point"; }
				uint32_t code_point() const { return cp; }
			};

			class invalid_utf8 : public std::exception {
				uint8_t u8;
			public:
				invalid_utf8(uint8_t u) : u8(u) {}
				virtual const char* what() const throw() { return "Invalid UTF-8"; }
				uint8_t utf8_octet() const { return u8; }
			};

			class invalid_utf16 : public std::exception {
				uint16_t u16;
			public:
				invalid_utf16(uint16_t u) : u16(u) {}
				virtual const char* what() const throw() { return "Invalid UTF-16"; }
				uint16_t utf16_word() const { return u16; }
			};

			class not_enough_room : public std::exception {
			public:
				virtual const char* what() const throw() { return "Not enough space"; }
			};

			/// The library API - functions intended to be called by the users

			template <typename octet_iterator, typename output_iterator>
			output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement)
			{
				while (start != end) {
					octet_iterator sequence_start = start;
					internal::utf_error err_code = internal::validate_next(start, end);
					switch (err_code) {
					case internal::OK:
						for (octet_iterator it = sequence_start; it != start; ++it)
							*out++ = *it;
						break;
					case internal::NOT_ENOUGH_ROOM:
						throw not_enough_room();
					case internal::INVALID_LEAD:
						append(replacement, out);
						++start;
						break;
					case internal::INCOMPLETE_SEQUENCE:
					case internal::OVERLONG_SEQUENCE:
					case internal::INVALID_CODE_POINT:
						append(replacement, out);
						++start;
						// just one replacement mark for the sequence
						while (internal::is_trail(*start) && start != end)
							++start;
						break;
					}
				}
				return out;
			}

			template <typename octet_iterator, typename output_iterator>
			inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out)
			{
				static const uint32_t replacement_marker = internal::mask16(0xfffd);
				return replace_invalid(start, end, out, replacement_marker);
			}

			template <typename octet_iterator>
			octet_iterator append(uint32_t cp, octet_iterator result)
			{
				if (!internal::is_code_point_valid(cp))
					throw invalid_code_point(cp);

				if (cp < 0x80)                        // one octet
					*(result++) = static_cast<uint8_t>(cp);
				else if (cp < 0x800) {                // two octets
					*(result++) = static_cast<uint8_t>((cp >> 6) | 0xc0);
					*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
				}
				else if (cp < 0x10000) {              // three octets
					*(result++) = static_cast<uint8_t>((cp >> 12) | 0xe0);
					*(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
					*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
				}
				else if (cp <= internal::CODE_POINT_MAX) {      // four octets
					*(result++) = static_cast<uint8_t>((cp >> 18) | 0xf0);
					*(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f) | 0x80);
					*(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
					*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
				}
				else
					throw invalid_code_point(cp);

				return result;
			}

			template <typename octet_iterator>
			uint32_t next(octet_iterator& it, octet_iterator end)
			{
				uint32_t cp = 0;
				internal::utf_error err_code = internal::validate_next(it, end, &cp);
				switch (err_code) {
				case internal::OK:
					break;
				case internal::NOT_ENOUGH_ROOM:
					throw not_enough_room();
				case internal::INVALID_LEAD:
				case internal::INCOMPLETE_SEQUENCE:
				case internal::OVERLONG_SEQUENCE:
					throw invalid_utf8(*it);
				case internal::INVALID_CODE_POINT:
					throw invalid_code_point(cp);
				}
				return cp;
			}

			template <typename octet_iterator>
			uint32_t peek_next(octet_iterator it, octet_iterator end)
			{
				return next(it, end);
			}

			template <typename octet_iterator>
			uint32_t prior(octet_iterator& it, octet_iterator start)
			{
				octet_iterator end = it;
				while (internal::is_trail(*(--it)))
				if (it < start)
					throw invalid_utf8(*it); // error - no lead byte in the sequence
				octet_iterator temp = it;
				return next(temp, end);
			}

			/// Deprecated in versions that include "prior"
			template <typename octet_iterator>
			uint32_t previous(octet_iterator& it, octet_iterator pass_start)
			{
				octet_iterator end = it;
				while (internal::is_trail(*(--it)))
				if (it == pass_start)
					throw invalid_utf8(*it); // error - no lead byte in the sequence
				octet_iterator temp = it;
				return next(temp, end);
			}

			template <typename octet_iterator, typename distance_type>
			void advance(octet_iterator& it, distance_type n, octet_iterator end)
			{
				for (distance_type i = 0; i < n; ++i)
					next(it, end);
			}

			template <typename octet_iterator>
			typename std::iterator_traits<octet_iterator>::difference_type
				distance(octet_iterator first, octet_iterator last)
			{
					typename std::iterator_traits<octet_iterator>::difference_type dist;
					for (dist = 0; first < last; ++dist)
						next(first, last);
					return dist;
				}

			template <typename u16bit_iterator, typename octet_iterator>
			octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result)
			{
				while (start != end) {
					uint32_t cp = internal::mask16(*start++);
					// Take care of surrogate pairs first
					if (internal::is_surrogate(cp)) {
						if (start != end) {
							uint32_t trail_surrogate = internal::mask16(*start++);
							if (trail_surrogate >= internal::TRAIL_SURROGATE_MIN && trail_surrogate <= internal::TRAIL_SURROGATE_MAX)
								cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
							else
								throw invalid_utf16(static_cast<uint16_t>(trail_surrogate));
						}
						else
							throw invalid_utf16(static_cast<uint16_t>(*start));

					}
					result = append(cp, result);
				}
				return result;
			}

			template <typename u16bit_iterator, typename octet_iterator>
			u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result)
			{
				while (start != end) {
					uint32_t cp = next(start, end);
					if (cp > 0xffff) { //make a surrogate pair
						*result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
						*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
					}
					else
						*result++ = static_cast<uint16_t>(cp);
				}
				return result;
			}

			template <typename octet_iterator, typename u32bit_iterator>
			octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result)
			{
				while (start != end)
					result = append(*(start++), result);

				return result;
			}

			template <typename octet_iterator, typename u32bit_iterator>
			u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result)
			{
				while (start < end)
					(*result++) = next(start, end);

				return result;
			}

			// The iterator class
			template <typename octet_iterator>
			class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {
				octet_iterator it;
				octet_iterator range_start;
				octet_iterator range_end;
			public:
				iterator() {}
				explicit iterator(const octet_iterator& octet_it,
					const octet_iterator& _range_start,
					const octet_iterator& _range_end) :
					it(octet_it), range_start(_range_start), range_end(_range_end)
				{
					if (it < range_start || it > range_end)
						throw std::out_of_range("Invalid utf-8 iterator position");
				}
				// the default "big three" are OK
				octet_iterator base() const { return it; }
				uint32_t operator * () const
				{
					octet_iterator temp = it;
					return next(temp, range_end);
				}
				bool operator == (const iterator& rhs) const
				{
					if (range_start != rhs.range_start || range_end != rhs.range_end)
						throw std::logic_error("Comparing utf-8 iterators defined with different ranges");
					return (it == rhs.it);
				}
				bool operator != (const iterator& rhs) const
				{
					return !(operator == (rhs));
				}
				iterator& operator ++ ()
				{
					next(it, range_end);
					return *this;
				}
				iterator operator ++ (int)
				{
					iterator temp = *this;
					next(it, range_end);
					return temp;
				}
				iterator& operator -- ()
				{
					prior(it, range_start);
					return *this;
				}
				iterator operator -- (int)
				{
					iterator temp = *this;
					prior(it, range_start);
					return temp;
				}
			}; // class iterator

			namespace unchecked
			{
				template <typename octet_iterator>
				octet_iterator append(uint32_t cp, octet_iterator result)
				{
					if (cp < 0x80)                        // one octet
						*(result++) = static_cast<char const>(cp);
					else if (cp < 0x800) {                // two octets
						*(result++) = static_cast<char const>((cp >> 6) | 0xc0);
						*(result++) = static_cast<char const>((cp & 0x3f) | 0x80);
					}
					else if (cp < 0x10000) {              // three octets
						*(result++) = static_cast<char const>((cp >> 12) | 0xe0);
						*(result++) = static_cast<char const>(((cp >> 6) & 0x3f) | 0x80);
						*(result++) = static_cast<char const>((cp & 0x3f) | 0x80);
					}
					else {                                // four octets
						*(result++) = static_cast<char const>((cp >> 18) | 0xf0);
						*(result++) = static_cast<char const>(((cp >> 12) & 0x3f) | 0x80);
						*(result++) = static_cast<char const>(((cp >> 6) & 0x3f) | 0x80);
						*(result++) = static_cast<char const>((cp & 0x3f) | 0x80);
					}
					return result;
				}

				template <typename octet_iterator>
				uint32_t next(octet_iterator& it)
				{
					uint32_t cp = internal::mask8(*it);
					typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it);
					switch (length) {
					case 1:
						break;
					case 2:
						it++;
						cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
						break;
					case 3:
						++it;
						cp = ((cp << 12) & 0xffff) + ((internal::mask8(*it) << 6) & 0xfff);
						++it;
						cp += (*it) & 0x3f;
						break;
					case 4:
						++it;
						cp = ((cp << 18) & 0x1fffff) + ((internal::mask8(*it) << 12) & 0x3ffff);
						++it;
						cp += (internal::mask8(*it) << 6) & 0xfff;
						++it;
						cp += (*it) & 0x3f;
						break;
					}
					++it;
					return cp;
				}

				template <typename octet_iterator>
				uint32_t peek_next(octet_iterator it)
				{
					return next(it);
				}

				template <typename octet_iterator>
				uint32_t prior(octet_iterator& it)
				{
					while (internal::is_trail(*(--it)));
					octet_iterator temp = it;
					return next(temp);
				}

				// Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous)
				template <typename octet_iterator>
				inline uint32_t previous(octet_iterator& it)
				{
					return prior(it);
				}

				template <typename octet_iterator, typename distance_type>
				void advance(octet_iterator& it, distance_type n)
				{
					for (distance_type i = 0; i < n; ++i)
						next(it);
				}

				template <typename octet_iterator>
				typename std::iterator_traits<octet_iterator>::difference_type
					distance(octet_iterator first, octet_iterator last)
				{
						typename std::iterator_traits<octet_iterator>::difference_type dist;
						for (dist = 0; first < last; ++dist)
							next(first);
						return dist;
					}

				template <typename u16bit_iterator, typename octet_iterator>
				octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result)
				{
					while (start != end) {
						uint32_t cp = internal::mask16(*start++);
						// Take care of surrogate pairs first
						if (internal::is_surrogate(cp)) {
							uint32_t trail_surrogate = internal::mask16(*start++);
							cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
						}
						result = append(cp, result);
					}
					return result;
				}

				template <typename u16bit_iterator, typename octet_iterator>
				u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result)
				{
					while (start != end) {
						uint32_t cp = next(start);
						if (cp > 0xffff) { //make a surrogate pair
							*result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
							*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
						}
						else
							*result++ = static_cast<uint16_t>(cp);
					}
					return result;
				}

				template <typename octet_iterator, typename u32bit_iterator>
				octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result)
				{
					while (start != end)
						result = append(*(start++), result);

					return result;
				}

				template <typename octet_iterator, typename u32bit_iterator>
				u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result)
				{
					while (start < end)
						(*result++) = next(start);

					return result;
				}

				// The iterator class
				template <typename octet_iterator>
				class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {
					octet_iterator it;
				public:
					iterator() {}
					explicit iterator(const octet_iterator& octet_it) : it(octet_it) {}
					// the default "big three" are OK
					octet_iterator base() const { return it; }
					uint32_t operator * () const
					{
						octet_iterator temp = it;
						return next(temp);
					}
					bool operator == (const iterator& rhs) const
					{
						return (it == rhs.it);
					}
					bool operator != (const iterator& rhs) const
					{
						return !(operator == (rhs));
					}
					iterator& operator ++ ()
					{
						std::advance(it, internal::sequence_length(it));
						return *this;
					}
					iterator operator ++ (int)
					{
						iterator temp = *this;
						std::advance(it, internal::sequence_length(it));
						return temp;
					}
					iterator& operator -- ()
					{
						prior(it);
						return *this;
					}
					iterator operator -- (int)
					{
						iterator temp = *this;
						prior(it);
						return temp;
					}
				}; // class iterator

			} // namespace utf8::unchecked
		} // namespace utf8

		namespace stbi
		{
			namespace decode
			{
				enum
				{
					STBI_default = 0, // only used for req_comp

					STBI_grey = 1,
					STBI_grey_alpha = 2,
					STBI_rgb = 3,
					STBI_rgb_alpha = 4
				};

				typedef unsigned char stbi_uc;
				//////////////////////////////////////////////////////////////////////////////
				//
				// 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;

				stbi_uc *stbi_load(char              const *filename, int *x, int *y, int *comp, int req_comp);
				stbi_uc *stbi_load_from_memory(stbi_uc           const *buffer, int len, int *x, int *y, int *comp, int req_comp);
				stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp);

#ifndef STBI_NO_STDIO
				stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp);
				// for stbi_load_from_file, file pointer is left pointing immediately after image
#endif

#ifndef STBI_NO_LINEAR
				float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp);
				float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
				float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp);

#ifndef STBI_NO_STDIO
				float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp);
#endif
#endif

#ifndef STBI_NO_HDR
				void   stbi_hdr_to_ldr_gamma(float gamma);
				void   stbi_hdr_to_ldr_scale(float scale);
#endif

#ifndef STBI_NO_LINEAR
				void   stbi_ldr_to_hdr_gamma(float gamma);
				void   stbi_ldr_to_hdr_scale(float scale);
#endif // STBI_NO_HDR

				// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
				int    stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
				int    stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
#ifndef STBI_NO_STDIO
				int      stbi_is_hdr(char const *filename);
				int      stbi_is_hdr_from_file(FILE *f);
#endif // STBI_NO_STDIO


				// get a VERY brief reason for failure
				// NOT THREADSAFE
				const char *stbi_failure_reason(void);

				// free the loaded image -- this is just free()
				void     stbi_image_free(void *retval_from_stbi_load);

				// get image dimensions & components without fully decoding
				int      stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
				int      stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);

#ifndef STBI_NO_STDIO
				int      stbi_info(char const *filename, int *x, int *y, int *comp);
				int      stbi_info_from_file(FILE *f, int *x, int *y, int *comp);

#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.
				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"
				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
				void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);

				// ZLIB client - used by PNG, available for other purposes

				char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
				char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
				char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
				int   stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);

				char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
				int   stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);

				// 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 <stdarg.h>
#include <stddef.h> // ptrdiff_t on osx
#include <stdlib.h>
#include <string.h>

#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
#include <math.h>  // ldexp
#endif

#ifndef STBI_NO_STDIO
#include <stdio.h>
#endif

#ifndef STBI_ASSERT
#include <assert.h>
#define STBI_ASSERT(x) assert(x)
#endif


#ifndef _MSC_VER
#ifdef __cplusplus
#define stbi_inline inline
#else
#define stbi_inline
#endif
#else
#define stbi_inline __forceinline
#endif


#ifdef _MSC_VER
				typedef unsigned short stbi__uint16;
				typedef   signed short stbi__int16;
				typedef unsigned int   stbi__uint32;
				typedef   signed int   stbi__int32;
#else
#include <stdint.h>
				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) >> (32 - (y))))
#endif

#if defined(STBI_MALLOC) && defined(STBI_FREE) && defined(STBI_REALLOC)
				// ok
#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC)
				// ok
#else
#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC."
#endif

#ifndef STBI_MALLOC
#define STBI_MALLOC(sz)    malloc(sz)
#define STBI_REALLOC(p,sz) realloc(p,sz)
#define STBI_FREE(p)       free(p)
#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(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
				// NOTE: not clear do we actually need this for the 64-bit path?
				// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
				// (but compiling with -msse2 allows the compiler to use SSE2 everywhere;
				// this is just broken and gcc are jerks for not fixing it properly
				// http://www.virtualdub.org/blog/pivot/entry.php?id=363 )
#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)
#define STBI_SSE2
#include <emmintrin.h>

#ifdef _MSC_VER

#if _MSC_VER >= 1400  // not VC6
#include <intrin.h> // __cpuid
				int stbi__cpuid3(void)
				{
					int info[4];
					__cpuid(info, 1);
					return info[3];
				}
#else
				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

				int stbi__sse2_available()
				{
					int info3 = stbi__cpuid3();
					return ((info3 >> 26) & 1) != 0;
				}
#else // assume GCC-style if not VC++
#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))

				int stbi__sse2_available()
				{
#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later
					// GCC 4.8+ has a nice way to do this
					return __builtin_cpu_supports("sse2");
#else
					// portable way to do this, preferably without using GCC inline ASM?
					// just bail for now.
					return 0;
#endif
				}
#endif
#endif

				// ARM NEON
#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
#undef STBI_NEON
#endif

#ifdef STBI_NEON
#include <arm_neon.h>
				// assume GCC or Clang on ARM targets
#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
#endif

#ifndef STBI_SIMD_ALIGN
#define STBI_SIMD_ALIGN(type, name) type name
#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];

					stbi_uc *img_buffer, *img_buffer_end;
					stbi_uc *img_buffer_original, *img_buffer_original_end;
				} stbi__context;


				void stbi__refill_buffer(stbi__context *s);

				// initialize a memory-decode context
				void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
				{
					s->io.read = NULL;
					s->read_from_callbacks = 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
				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->img_buffer_original = s->buffer_start;
					stbi__refill_buffer(s);
					s->img_buffer_original_end = s->img_buffer_end;
				}

#ifndef STBI_NO_STDIO

				int stbi__stdio_read(void *user, char *data, int size)
				{
					return (int)fread(data, 1, size, (FILE*)user);
				}

				void stbi__stdio_skip(void *user, int n)
				{
					fseek((FILE*)user, n, SEEK_CUR);
				}

				int stbi__stdio_eof(void *user)
				{
					return feof((FILE*)user);
				}

				stbi_io_callbacks stbi__stdio_callbacks =
				{
					stbi__stdio_read,
					stbi__stdio_skip,
					stbi__stdio_eof,
				};

				void stbi__start_file(stbi__context *s, FILE *f)
				{
					stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *)f);
				}

				// void stop_file(stbi__context *s) { }

#endif // !STBI_NO_STDIO

				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;
				}

#ifndef STBI_NO_JPEG
				int      stbi__jpeg_test(stbi__context *s);
				stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_PNG
				int      stbi__png_test(stbi__context *s);
				stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_BMP
				int      stbi__bmp_test(stbi__context *s);
				stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_TGA
				int      stbi__tga_test(stbi__context *s);
				stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_PSD
				int      stbi__psd_test(stbi__context *s);
				stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_HDR
				int      stbi__hdr_test(stbi__context *s);
				float   *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_PIC
				int      stbi__pic_test(stbi__context *s);
				stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_GIF
				int      stbi__gif_test(stbi__context *s);
				stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
#endif

#ifndef STBI_NO_PNM
				int      stbi__pnm_test(stbi__context *s);
				stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
				int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
#endif

				// this is not threadsafe
				const char *stbi__g_failure_reason;

				const char *stbi_failure_reason(void)
				{
					return stbi__g_failure_reason;
				}

				int stbi__err(const char *str)
				{
					stbi__g_failure_reason = str;
					return 0;
				}

				void *stbi__malloc(size_t size)
				{
					return STBI_MALLOC(size);
				}

				// 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))

				void stbi_image_free(void *retval_from_stbi_load)
				{
					STBI_FREE(retval_from_stbi_load);
				}

#ifndef STBI_NO_LINEAR
				float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
#endif

#ifndef STBI_NO_HDR
				stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp);
#endif

				int stbi__vertically_flip_on_load = 0;

				void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
				{
					stbi__vertically_flip_on_load = flag_true_if_should_flip;
				}

				unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
#ifndef STBI_NO_JPEG
					if (stbi__jpeg_test(s)) return stbi__jpeg_load(s, x, y, comp, req_comp);
#endif
#ifndef STBI_NO_PNG
					if (stbi__png_test(s))  return stbi__png_load(s, x, y, comp, req_comp);
#endif
#ifndef STBI_NO_BMP
					if (stbi__bmp_test(s))  return stbi__bmp_load(s, x, y, comp, req_comp);
#endif
#ifndef STBI_NO_GIF
					if (stbi__gif_test(s))  return stbi__gif_load(s, x, y, comp, req_comp);
#endif
#ifndef STBI_NO_PSD
					if (stbi__psd_test(s))  return stbi__psd_load(s, x, y, comp, req_comp);
#endif
#ifndef STBI_NO_PIC
					if (stbi__pic_test(s))  return stbi__pic_load(s, x, y, comp, req_comp);
#endif
#ifndef STBI_NO_PNM
					if (stbi__pnm_test(s))  return stbi__pnm_load(s, x, y, comp, req_comp);
#endif

#ifndef STBI_NO_HDR
					if (stbi__hdr_test(s)) {
						float *hdr = stbi__hdr_load(s, x, y, comp, req_comp);
						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);
#endif

					return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
				}

				unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					unsigned char *result = stbi__load_main(s, x, y, comp, req_comp);

					if (stbi__vertically_flip_on_load && result != NULL) {
						int w = *x, h = *y;
						int depth = req_comp ? req_comp : *comp;
						int row, col, z;
						stbi_uc temp;

						// @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once
						for (row = 0; row < (h >> 1); row++) {
							for (col = 0; col < w; col++) {
								for (z = 0; z < depth; z++) {
									temp = result[(row * w + col) * depth + z];
									result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z];
									result[((h - row - 1) * w + col) * depth + z] = temp;
								}
							}
						}
					}

					return result;
				}

#ifndef STBI_NO_HDR
				void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
				{
					if (stbi__vertically_flip_on_load && result != NULL) {
						int w = *x, h = *y;
						int depth = req_comp ? req_comp : *comp;
						int row, col, z;
						float temp;

						// @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once
						for (row = 0; row < (h >> 1); row++) {
							for (col = 0; col < w; col++) {
								for (z = 0; z < depth; z++) {
									temp = result[(row * w + col) * depth + z];
									result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z];
									result[((h - row - 1) * w + col) * depth + z] = temp;
								}
							}
						}
					}
				}
#endif

#ifndef STBI_NO_STDIO

				FILE *stbi__fopen(char const *filename, char const *mode)
				{
					FILE *f;
#if defined(_MSC_VER) && _MSC_VER >= 1400
					if (0 != fopen_s(&f, filename, mode))
						f = 0;
#else
					f = fopen(filename, mode);
#endif
					return f;
				}


				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;
				}

				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_flip(&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;
				}
#endif //!STBI_NO_STDIO

				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_flip(&s, x, y, comp, req_comp);
				}

				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_flip(&s, x, y, comp, req_comp);
				}

#ifndef STBI_NO_LINEAR
				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)) {
						float *hdr_data = stbi__hdr_load(s, x, y, comp, req_comp);
						if (hdr_data)
							stbi__float_postprocess(hdr_data, x, y, comp, req_comp);
						return hdr_data;
					}
#endif
					data = stbi__load_flip(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");
				}

				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);
				}

				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
				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;
				}

				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!

				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
				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;
				}

				int      stbi_is_hdr_from_file(FILE *f)
				{
#ifndef STBI_NO_HDR
					stbi__context s;
					stbi__start_file(&s, f);
					return stbi__hdr_test(&s);
#else
					STBI_NOTUSED(f);
					return 0;
#endif
				}
#endif // !STBI_NO_STDIO

				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
				}

				float stbi__h2l_gamma_i = 1.0f / 2.2f, stbi__h2l_scale_i = 1.0f;
				float stbi__l2h_gamma = 2.2f, stbi__l2h_scale = 1.0f;

#ifndef STBI_NO_LINEAR
				void   stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
				void   stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
#endif

				void   stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1 / gamma; }
				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
				};

				void stbi__refill_buffer(stbi__context *s)
				{
					int n = (s->io.read)(s->io_user_data, (char*)s->buffer_start, s->buflen);
					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  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;
				}

				stbi_inline  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;
				}

				void stbi__skip(stbi__context *s, int n)
				{
					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;
				}

				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;
				}

				int stbi__get16be(stbi__context *s)
				{
					int z = stbi__get8(s);
					return (z << 8) + stbi__get8(s);
				}

				stbi__uint32 stbi__get32be(stbi__context *s)
				{
					stbi__uint32 z = stbi__get16be(s);
					return (z << 16) + stbi__get16be(s);
				}

#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
				// nothing
#else
				int stbi__get16le(stbi__context *s)
				{
					int z = stbi__get8(s);
					return z + (stbi__get8(s) << 8);
				}
#endif

#ifndef STBI_NO_BMP
				stbi__uint32 stbi__get32le(stbi__context *s)
				{
					stbi__uint32 z = stbi__get16le(s);
					return z + (stbi__get16le(s) << 16);
				}
#endif

#define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings


				//////////////////////////////////////////////////////////////////////////////
				//
				//  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

				stbi_uc stbi__compute_y(int r, int g, int b)
				{
					return (stbi_uc)(((r * 77) + (g * 150) + (29 * b)) >> 8);
				}

				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(req_comp * x * y);
					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 COMBO(a,b)  ((a)*8+(b))
#define CASE(a,b)   case 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 (COMBO(img_n, req_comp)) {
							CASE(1, 2) dest[0] = src[0], dest[1] = 255; break;
							CASE(1, 3) dest[0] = dest[1] = dest[2] = src[0]; break;
							CASE(1, 4) dest[0] = dest[1] = dest[2] = src[0], dest[3] = 255; break;
							CASE(2, 1) dest[0] = src[0]; break;
							CASE(2, 3) dest[0] = dest[1] = dest[2] = src[0]; break;
							CASE(2, 4) dest[0] = dest[1] = dest[2] = src[0], dest[3] = src[1]; break;
							CASE(3, 4) dest[0] = src[0], dest[1] = src[1], dest[2] = src[2], dest[3] = 255; break;
							CASE(3, 1) dest[0] = stbi__compute_y(src[0], src[1], src[2]); break;
							CASE(3, 2) dest[0] = stbi__compute_y(src[0], src[1], src[2]), dest[1] = 255; break;
							CASE(4, 1) dest[0] = stbi__compute_y(src[0], src[1], src[2]); break;
							CASE(4, 2) dest[0] = stbi__compute_y(src[0], src[1], src[2]), dest[1] = src[3]; break;
							CASE(4, 3) dest[0] = src[0], dest[1] = src[1], dest[2] = src[2]; break;
						default: STBI_ASSERT(0);
						}
#undef CASE
					}

					STBI_FREE(data);
					return good;
				}

#ifndef STBI_NO_LINEAR
				float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
				{
					int i, k, n;
					float *output = (float *)stbi__malloc(x * y * comp * sizeof(float));
					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 (k < comp) output[i*comp + k] = data[i*comp + k] / 255.0f;
					}
					STBI_FREE(data);
					return output;
				}
#endif

#ifndef STBI_NO_HDR
#define stbi__float2int(x)   ((int) (x))
				stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp)
				{
					int i, k, n;
					stbi_uc *output = (stbi_uc *)stbi__malloc(x * y * comp);
					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_uc 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 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;

				int stbi__build_huffman(stbi__huffman *h, int *count)
				{
					int i, j, k = 0, 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);
					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 >= (1 << 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.
				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 += (-1 << 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 << 8) + (run << 4) + (len + magbits));
							}
						}
					}
				}

				void stbi__grow_buffer_unsafe(stbi__jpeg *j)
				{
					do {
						int b = j->nomore ? 0 : stbi__get8(j->s);
						if (b == 0xff) {
							int c = stbi__get8(j->s);
							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
				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  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];
					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<<n) + 1
				int const stbi__jbias[16] = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 };

				// combined JPEG 'receive' and JPEG 'extend', since baseline
				// always extends everything it receives.
				stbi_inline  int stbi__extend_receive(stbi__jpeg *j, int n)
				{
					unsigned int k;
					int sgn;
					if (j->code_bits < n) stbi__grow_buffer_unsafe(j);

					sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB
					k = stbi_lrot(j->code_buffer, n);
					STBI_ASSERT(n >= 0 && n < (int)(sizeof(stbi__bmask) / sizeof(*stbi__bmask)));
					j->code_buffer = k & ~stbi__bmask[n];
					k &= stbi__bmask[n];
					j->code_bits -= n;
					return k + (stbi__jbias[n] & ~sgn);
				}

				// get some unsigned bits
				stbi_inline  int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
				{
					unsigned int k;
					if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
					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  int stbi__jpeg_get_bit(stbi__jpeg *j)
				{
					unsigned int k;
					if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
					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?
				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--
				int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *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) 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;
					dc = j->img_comp[b].dc_pred + diff;
					j->img_comp[b].dc_pred = dc;
					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
							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;
				}

				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);
						diff = t ? stbi__extend_receive(j, t) : 0;

						dc = j->img_comp[b].dc_pred + diff;
						j->img_comp[b].dc_pred = dc;
						data[0] = (short)(dc << 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
				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
								j->code_buffer <<= s;
								j->code_bits -= s;
								zig = stbi__jpeg_dezigzag[k++];
								data[zig] = (short)((r >> 8) << 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) << 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  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) << 12)

				// 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;

				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] << 2;
							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".
				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.
				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
				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);
					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
				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 = 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
				}

				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;
						}
					}
				}

				void stbi__jpeg_dequantize(short *data, stbi_uc *dequant)
				{
					int i;
					for (i = 0; i < 64; ++i)
						data[i] *= dequant[i];
				}

				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);
								}
							}
						}
					}
				}

				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;
							int t = q & 15, i;
							if (p != 0) 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__get8(z->s);
							L -= 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];
							}
							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) {
						stbi__skip(z->s, stbi__get16be(z->s) - 2);
						return 1;
					}
					return 0;
				}

				// after we see SOS
				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;
				}

				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
					c = stbi__get8(s);
					if (c != 3 && c != 1) return stbi__err("bad component count", "Corrupt JPEG");    // JFIF requires
					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");

					for (i = 0; i < s->img_n; ++i) {
						z->img_comp[i].id = stbi__get8(s);
						if (z->img_comp[i].id != i + 1)   // JFIF requires
						if (z->img_comp[i].id != i)  // some version of jpegtran outputs non-JFIF-compliant files!
							return stbi__err("bad component ID", "Corrupt JPEG");
						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 ((1 << 30) / s->img_x / s->img_n < s->img_y) 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;
					}

					// 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;
					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
						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].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2 + 15);

						if (z->img_comp[i].raw_data == NULL) {
							for (--i; i >= 0; --i) {
								STBI_FREE(z->img_comp[i].raw_data);
								z->img_comp[i].raw_data = NULL;
							}
							return 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);
						z->img_comp[i].linebuf = NULL;
						if (z->progressive) {
							z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3;
							z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3;
							z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short)+15);
							z->img_comp[i].coeff = (short*)(((size_t)z->img_comp[i].raw_coeff + 15) & ~15);
						}
						else {
							z->img_comp[i].coeff = 0;
							z->img_comp[i].raw_coeff = 0;
						}
					}

					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)

				int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
				{
					int m;
					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;
				}

				// decode image to YCbCr format
				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) {
								// handle 0s at the end of image data from IP Kamera 9060
								while (!stbi__at_eof(j->s)) {
									int x = stbi__get8(j->s);
									if (x == 255) {
										j->marker = stbi__get8(j->s);
										break;
									}
									else if (x != 0) {
										return stbi__err("junk before marker", "Corrupt JPEG");
									}
								}
								// if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
							}
						}
						else {
							if (!stbi__process_marker(j, m)) return 0;
						}
						m = stbi__get_marker(j);
					}
					if (j->progressive)
						stbi__jpeg_finish(j);
					return 1;
				}

				//  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))

				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;
				}

				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;
				}

				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))

				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)
				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

				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;
				}

#ifdef STBI_JPEG_OLD
				// this is the same YCbCr-to-RGB calculation that stb_image has used
				// historically before the algorithm changes in 1.49
#define float2fixed(x)  ((int) ((x) * 65536 + 0.5))
				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] << 16) + 32768; // rounding
						int r, g, b;
						int cr = pcr[i] - 128;
						int cb = pcb[i] - 128;
						r = y_fixed + cr*float2fixed(1.40200f);
						g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f);
						b = y_fixed + cb*float2fixed(1.77200f);
						r >>= 16;
						g >>= 16;
						b >>= 16;
						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;
					}
				}
#else
				// 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 float2fixed(x)  (((int) ((x) * 4096.0f + 0.5f)) << 8)
				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* float2fixed(1.40200f);
						g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000);
						b = y_fixed + cb* 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

#if defined(STBI_SSE2) || defined(STBI_NEON)
				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* float2fixed(1.40200f);
						g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000);
						b = y_fixed + cb* 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
				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;
#ifndef STBI_JPEG_OLD
						j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
#endif
						j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
					}
#endif

#ifdef STBI_NEON
					j->idct_block_kernel = stbi__idct_simd;
#ifndef STBI_JPEG_OLD
					j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
#endif
					j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
#endif
				}

				// clean up the temporary component buffers
				void stbi__cleanup_jpeg(stbi__jpeg *j)
				{
					int i;
					for (i = 0; i < j->s->img_n; ++i) {
						if (j->img_comp[i].raw_data) {
							STBI_FREE(j->img_comp[i].raw_data);
							j->img_comp[i].raw_data = NULL;
							j->img_comp[i].data = NULL;
						}
						if (j->img_comp[i].raw_coeff) {
							STBI_FREE(j->img_comp[i].raw_coeff);
							j->img_comp[i].raw_coeff = 0;
							j->img_comp[i].coeff = 0;
						}
						if (j->img_comp[i].linebuf) {
							STBI_FREE(j->img_comp[i].linebuf);
							j->img_comp[i].linebuf = NULL;
						}
					}
				}

				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;

				stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
				{
					int n, decode_n;
					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;

					if (z->s->img_n == 3 && n < 3)
						decode_n = 1;
					else
						decode_n = z->s->img_n;

					// resample and color-convert
					{
						int k;
						unsigned int i, j;
						stbi_uc *output;
						stbi_uc *coutput[4];

						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(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) {
									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 {
								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; // report original components, not output
						return output;
					}
				}

				unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					stbi__jpeg j;
					j.s = s;
					stbi__setup_jpeg(&j);
					return load_jpeg_image(&j, x, y, comp, req_comp);
				}

				int stbi__jpeg_test(stbi__context *s)
				{
					int r;
					stbi__jpeg j;
					j.s = s;
					stbi__setup_jpeg(&j);
					r = stbi__decode_jpeg_header(&j, STBI__SCAN_type);
					stbi__rewind(s);
					return r;
				}

				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;
					return 1;
				}

				int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
				{
					stbi__jpeg j;
					j.s = s;
					return stbi__jpeg_info_raw(&j, x, y, comp);
				}
#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)

				// 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[288];
					stbi__uint16 value[288];
				} stbi__zhuffman;

				stbi_inline  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  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);
				}

				int stbi__zbuild_huffman(stbi__zhuffman *z, 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;
					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  stbi_uc stbi__zget8(stbi__zbuf *z)
				{
					if (z->zbuffer >= z->zbuffer_end) return 0;
					return *z->zbuffer++;
				}

				void stbi__fill_bits(stbi__zbuf *z)
				{
					do {
						STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
						z->code_buffer |= (unsigned int)stbi__zget8(z) << z->num_bits;
						z->num_bits += 8;
					} while (z->num_bits <= 24);
				}

				stbi_inline  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;
				}

				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];
					STBI_ASSERT(z->size[b] == s);
					a->code_buffer >>= s;
					a->num_bits -= s;
					return z->value[b];
				}

				stbi_inline  int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
				{
					int b, s;
					if (a->num_bits < 16) 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);
				}

				int stbi__zexpand(stbi__zbuf *z, char *zout, int n)  // need to make room for n bytes
				{
					char *q;
					int cur, limit;
					z->zout = zout;
					if (!z->z_expandable) return stbi__err("output buffer limit", "Corrupt PNG");
					cur = (int)(z->zout - z->zout_start);
					limit = (int)(z->zout_end - z->zout_start);
					while (cur + n > limit)
						limit *= 2;
					q = (char *)STBI_REALLOC(z->zout_start, 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;
				}

				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 };

				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 };

				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 };

				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 };

				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;
								return 1;
							}
							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) return stbi__err("bad huffman code", "Corrupt PNG");
							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 (zout + len > a->zout_end) {
								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); }
							}
						}
					}
				}

				int stbi__compute_huffman_codes(stbi__zbuf *a)
				{
					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;

					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 < hlit + hdist) {
						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 if (c == 16) {
							c = stbi__zreceive(a, 2) + 3;
							memset(lencodes + n, lencodes[n - 1], c);
							n += c;
						}
						else if (c == 17) {
							c = stbi__zreceive(a, 3) + 3;
							memset(lencodes + n, 0, c);
							n += c;
						}
						else {
							STBI_ASSERT(c == 18);
							c = stbi__zreceive(a, 7) + 11;
							memset(lencodes + n, 0, c);
							n += c;
						}
					}
					if (n != hlit + hdist) 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;
				}

				int stbi__parse_uncomperssed_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;
					}
					STBI_ASSERT(a->num_bits == 0);
					// 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;
				}

				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 ((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;
				}

				// @TODO: should statically initialize these for optimal thread safety
				stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32];
				void stbi__init_zdefaults(void)
				{
					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;
				}

				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;
					do {
						final = stbi__zreceive(a, 1);
						type = stbi__zreceive(a, 2);
						if (type == 0) {
							if (!stbi__parse_uncomperssed_block(a)) return 0;
						}
						else if (type == 3) {
							return 0;
						}
						else {
							if (type == 1) {
								// use fixed code lengths
								if (!stbi__zdefault_distance[31]) stbi__init_zdefaults();
								if (!stbi__zbuild_huffman(&a->z_length, stbi__zdefault_length, 288)) 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;
				}

				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);
				}

				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;
					}
				}

				char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
				{
					return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
				}

				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;
					}
				}

				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;
				}

				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;
					}
				}

				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;

				stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
				{
					stbi__pngchunk c;
					c.length = stbi__get32be(s);
					c.type = stbi__get32be(s);
					return c;
				}

				int stbi__check_png_header(stbi__context *s)
				{
					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;
				} stbi__png;


				enum {
					STBI__F_none = 0,
					STBI__F_sub = 1,
					STBI__F_up = 2,
					STBI__F_avg = 3,
					STBI__F_paeth = 4,
					// synthetic filters used for first scanline to avoid needing a dummy row of 0s
					STBI__F_avg_first,
					STBI__F_paeth_first
				};

				stbi_uc first_row_filter[5] =
				{
					STBI__F_none,
					STBI__F_sub,
					STBI__F_none,
					STBI__F_avg_first,
					STBI__F_paeth_first
				};

				int stbi__paeth(int a, int b, int c)
				{
					int p = a + b - c;
					int pa = abs(p - a);
					int pb = abs(p - b);
					int pc = abs(p - c);
					if (pa <= pb && pa <= pc) return a;
					if (pb <= pc) return b;
					return c;
				}

				stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0, 0, 0, 0x01 };

				// create the png data from post-deflated data
				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)
				{
					stbi__context *s = a->s;
					stbi__uint32 i, j, stride = x*out_n;
					stbi__uint32 img_len, img_width_bytes;
					int k;
					int img_n = s->img_n; // copy it into a local for later

					STBI_ASSERT(out_n == s->img_n || out_n == s->img_n + 1);
					a->out = (stbi_uc *)stbi__malloc(x * y * out_n); // extra bytes to write off the end into
					if (!a->out) return stbi__err("outofmem", "Out of memory");

					img_width_bytes = (((img_n * x * depth) + 7) >> 3);
					img_len = (img_width_bytes + 1) * y;
					if (s->img_x == x && s->img_y == y) {
						if (raw_len != img_len) return stbi__err("not enough pixels", "Corrupt PNG");
					}
					else { // interlaced:
						if (raw_len < img_len) return stbi__err("not enough pixels", "Corrupt PNG");
					}

					for (j = 0; j < y; ++j) {
						stbi_uc *cur = a->out + stride*j;
						stbi_uc *prior = cur - stride;
						int filter = *raw++;
						int filter_bytes = img_n;
						int width = x;
						if (filter > 4)
							return stbi__err("invalid filter", "Corrupt PNG");

						if (depth < 8) {
							STBI_ASSERT(img_width_bytes <= x);
							cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
							filter_bytes = 1;
							width = img_width_bytes;
						}

						// if first row, use special filter that doesn't sample previous row
						if (j == 0) filter = first_row_filter[filter];

						// handle first byte explicitly
						for (k = 0; k < filter_bytes; ++k) {
							switch (filter) {
							case STBI__F_none: cur[k] = raw[k]; break;
							case STBI__F_sub: cur[k] = raw[k]; break;
							case STBI__F_up: cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
							case STBI__F_avg: cur[k] = STBI__BYTECAST(raw[k] + (prior[k] >> 1)); break;
							case STBI__F_paeth: cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0, prior[k], 0)); break;
							case STBI__F_avg_first: cur[k] = raw[k]; break;
							case STBI__F_paeth_first: cur[k] = raw[k]; break;
							}
						}

						if (depth == 8) {
							if (img_n != out_n)
								cur[img_n] = 255; // first pixel
							raw += img_n;
							cur += out_n;
							prior += out_n;
						}
						else {
							raw += 1;
							cur += 1;
							prior += 1;
						}

						// this is a little gross, so that we don't switch per-pixel or per-component
						if (depth < 8 || img_n == out_n) {
							int nk = (width - 1)*img_n;
#define CASE(f) \
					case f:     \
							for (k = 0; k < nk; ++k)
							switch (filter) {
								// "none" filter turns into a memcpy here; make that explicit.
							case STBI__F_none:         memcpy(cur, raw, nk); break;
								CASE(STBI__F_sub)          cur[k] = STBI__BYTECAST(raw[k] + cur[k - filter_bytes]); break;
								CASE(STBI__F_up)           cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
								CASE(STBI__F_avg)          cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k - filter_bytes]) >> 1)); break;
								CASE(STBI__F_paeth)        cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k - filter_bytes], prior[k], prior[k - filter_bytes])); break;
								CASE(STBI__F_avg_first)    cur[k] = STBI__BYTECAST(raw[k] + (cur[k - filter_bytes] >> 1)); break;
								CASE(STBI__F_paeth_first)  cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k - filter_bytes], 0, 0)); break;
							}
#undef CASE
							raw += nk;
						}
						else {
							STBI_ASSERT(img_n + 1 == out_n);
#define CASE(f) \
					case f:     \
							for (i = x - 1; i >= 1; --i, cur[img_n] = 255, raw += img_n, cur += out_n, prior += out_n) \
							for (k = 0; k < img_n; ++k)
							switch (filter) {
								CASE(STBI__F_none)         cur[k] = raw[k]; break;
								CASE(STBI__F_sub)          cur[k] = STBI__BYTECAST(raw[k] + cur[k - out_n]); break;
								CASE(STBI__F_up)           cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
								CASE(STBI__F_avg)          cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k - out_n]) >> 1)); break;
								CASE(STBI__F_paeth)        cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k - out_n], prior[k], prior[k - out_n])); break;
								CASE(STBI__F_avg_first)    cur[k] = STBI__BYTECAST(raw[k] + (cur[k - out_n] >> 1)); break;
								CASE(STBI__F_paeth_first)  cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k - out_n], 0, 0)); break;
							}
#undef CASE
						}
					}

					// we make a separate pass to expand bits to pixels; for performance,
					// this could run two scanlines behind the above code, so it won't
					// intefere with filtering but will still be in the cache.
					if (depth < 8) {
						for (j = 0; j < y; ++j) {
							stbi_uc *cur = a->out + stride*j;
							stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
							// unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
							// png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
							stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range

							// note that the final byte might overshoot and write more data than desired.
							// we can allocate enough data that this never writes out of memory, but it
							// could also overwrite the next scanline. can it overwrite non-empty data
							// on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
							// so we need to explicitly clamp the final ones

							if (depth == 4) {
								for (k = x*img_n; k >= 2; k -= 2, ++in) {
									*cur++ = scale * ((*in >> 4));
									*cur++ = scale * ((*in) & 0x0f);
								}
								if (k > 0) *cur++ = scale * ((*in >> 4));
							}
							else if (depth == 2) {
								for (k = x*img_n; k >= 4; k -= 4, ++in) {
									*cur++ = scale * ((*in >> 6));
									*cur++ = scale * ((*in >> 4) & 0x03);
									*cur++ = scale * ((*in >> 2) & 0x03);
									*cur++ = scale * ((*in) & 0x03);
								}
								if (k > 0) *cur++ = scale * ((*in >> 6));
								if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
								if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
							}
							else if (depth == 1) {
								for (k = x*img_n; k >= 8; k -= 8, ++in) {
									*cur++ = scale * ((*in >> 7));
									*cur++ = scale * ((*in >> 6) & 0x01);
									*cur++ = scale * ((*in >> 5) & 0x01);
									*cur++ = scale * ((*in >> 4) & 0x01);
									*cur++ = scale * ((*in >> 3) & 0x01);
									*cur++ = scale * ((*in >> 2) & 0x01);
									*cur++ = scale * ((*in >> 1) & 0x01);
									*cur++ = scale * ((*in) & 0x01);
								}
								if (k > 0) *cur++ = scale * ((*in >> 7));
								if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
								if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
								if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
								if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
								if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
								if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
							}
							if (img_n != out_n) {
								int q;
								// insert alpha = 255
								cur = a->out + stride*j;
								if (img_n == 1) {
									for (q = x - 1; q >= 0; --q) {
										cur[q * 2 + 1] = 255;
										cur[q * 2 + 0] = cur[q];
									}
								}
								else {
									STBI_ASSERT(img_n == 3);
									for (q = x - 1; q >= 0; --q) {
										cur[q * 4 + 3] = 255;
										cur[q * 4 + 2] = cur[q * 3 + 2];
										cur[q * 4 + 1] = cur[q * 3 + 1];
										cur[q * 4 + 0] = cur[q * 3 + 0];
									}
								}
							}
						}
					}

					return 1;
				}

				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)
				{
					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(a->s->img_x * a->s->img_y * out_n);
					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_n + out_x*out_n,
										a->out + (j*x + i)*out_n, out_n);
								}
							}
							STBI_FREE(a->out);
							image_data += img_len;
							image_data_len -= img_len;
						}
					}
					a->out = final;

					return 1;
				}

				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;
				}

				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(pixel_count * pal_img_n);
					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;
				}

				int stbi__unpremultiply_on_load = 0;
				int stbi__de_iphone_flag = 0;

				void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
				{
					stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply;
				}

				void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
				{
					stbi__de_iphone_flag = flag_true_if_should_convert;
				}

				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) {
									p[0] = p[2] * 255 / a;
									p[1] = p[1] * 255 / a;
									p[2] = t * 255 / 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)  (((a) << 24) + ((b) << 16) + ((c) << 8) + (d))

				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];
					stbi__uint32 ioff = 0, idata_limit = 0, i, pal_len = 0;
					int first = 1, k, interlace = 0, color = 0, depth = 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); if (s->img_x > (1 << 24)) return stbi__err("too large", "Very large image (corrupt?)");
																	 s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large", "Very large image (corrupt?)");
																	 depth = stbi__get8(s);  if (depth != 1 && depth != 2 && depth != 4 && depth != 8)  return stbi__err("1/2/4/8-bit only", "PNG not supported: 1/2/4/8-bit only");
																	 color = stbi__get8(s);  if (color > 6)         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");
																		 if (scan == STBI__SCAN_header) return 1;
																	 }
																	 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");
																		 // if 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;
																		 for (k = 0; k < s->img_n; ++k)
																			 tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[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) { s->img_n = pal_img_n; return 1; }
																	 if ((int)(ioff + c.length) < (int)ioff) return 0;
																	 if (ioff + c.length > 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;
																		 p = (stbi_uc *)STBI_REALLOC(z->idata, 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 * 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, depth, color, interlace)) return 0;
																	 if (has_trans)
																	 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;
																	 }
																	 STBI_FREE(z->expanded); z->expanded = NULL;
																	 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
								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);
					}
				}

				unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp)
				{
					unsigned char *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)) {
						result = p->out;
						p->out = NULL;
						if (req_comp && req_comp != p->s->img_out_n) {
							result = stbi__convert_format(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_out_n;
					}
					STBI_FREE(p->out);      p->out = NULL;
					STBI_FREE(p->expanded); p->expanded = NULL;
					STBI_FREE(p->idata);    p->idata = NULL;

					return result;
				}

				unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					stbi__png p;
					p.s = s;
					return stbi__do_png(&p, x, y, comp, req_comp);
				}

				int stbi__png_test(stbi__context *s)
				{
					int r;
					r = stbi__check_png_header(s);
					stbi__rewind(s);
					return r;
				}

				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;
				}

				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);
				}
#endif

				// Microsoft/Windows BMP image

#ifndef STBI_NO_BMP
				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;
				}

				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
				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, z >>= 1;
					return n;
				}

				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;
				}

				int stbi__shiftsigned(int v, int shift, int bits)
				{
					int result;
					int z = 0;

					if (shift < 0) v <<= -shift;
					else v >>= shift;
					result = v;

					z = bits;
					while (z < 8) {
						result += v >> z;
						z += bits;
					}
					return result;
				}

				stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					stbi_uc *out;
					unsigned int mr = 0, mg = 0, mb = 0, ma = 0, all_a = 255;
					stbi_uc pal[256][4];
					int psize = 0, i, j, compress = 0, width;
					int bpp, flip_vertically, pad, target, offset, 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
					offset = stbi__get32le(s);
					hsz = stbi__get32le(s);
					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");
					bpp = stbi__get16le(s);
					if (bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit");
					flip_vertically = ((int)s->img_y) > 0;
					s->img_y = abs((int)s->img_y);
					if (hsz == 12) {
						if (bpp < 24)
							psize = (offset - 14 - 24) / 3;
					}
					else {
						compress = stbi__get32le(s);
						if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
						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 (bpp == 16 || bpp == 32) {
								mr = mg = mb = 0;
								if (compress == 0) {
									if (bpp == 32) {
										mr = 0xffu << 16;
										mg = 0xffu << 8;
										mb = 0xffu << 0;
										ma = 0xffu << 24;
										all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
									}
									else {
										mr = 31u << 10;
										mg = 31u << 5;
										mb = 31u << 0;
									}
								}
								else if (compress == 3) {
									mr = stbi__get32le(s);
									mg = stbi__get32le(s);
									mb = stbi__get32le(s);
									// not documented, but generated by photoshop and handled by mspaint
									if (mr == mg && mg == mb) {
										// ?!?!?
										return stbi__errpuc("bad BMP", "bad BMP");
									}
								}
								else
									return stbi__errpuc("bad BMP", "bad BMP");
							}
						}
						else {
							STBI_ASSERT(hsz == 108 || hsz == 124);
							mr = stbi__get32le(s);
							mg = stbi__get32le(s);
							mb = stbi__get32le(s);
							ma = stbi__get32le(s);
							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
							}
						}
						if (bpp < 16)
							psize = (offset - 14 - hsz) >> 2;
					}
					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
					out = (stbi_uc *)stbi__malloc(target * s->img_x * s->img_y);
					if (!out) return stbi__errpuc("outofmem", "Out of memory");
					if (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 (hsz != 12) stbi__get8(s);
							pal[i][3] = 255;
						}
						stbi__skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4));
						if (bpp == 4) width = (s->img_x + 1) >> 1;
						else if (bpp == 8) width = s->img_x;
						else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
						pad = (-width) & 3;
						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 (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 = (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, offset - 14 - hsz);
						if (bpp == 24) width = 3 * s->img_x;
						else if (bpp == 16) width = 2 * s->img_x;
						else /* bpp = 32 and pad = 0 */ width = 0;
						pad = (-width) & 3;
						if (bpp == 24) {
							easy = 1;
						}
						else if (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);
						}
						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 {
								for (i = 0; i < (int)s->img_x; ++i) {
									stbi__uint32 v = (bpp == 16 ? (stbi__uint32)stbi__get16le(s) : stbi__get32le(s));
									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
				int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
				{
					int tga_w, tga_h, tga_comp;
					int sz;
					stbi__get8(s);                   // discard Offset
					sz = stbi__get8(s);              // color type
					if (sz > 1) {
						stbi__rewind(s);
						return 0;      // only RGB or indexed allowed
					}
					sz = stbi__get8(s);              // image type
					// only RGB or grey allowed, +/- RLE
					if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0;
					stbi__skip(s, 9);
					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
					}
					sz = stbi__get8(s);               // bits per pixel
					// only RGB or RGBA or grey allowed
					if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) {
						stbi__rewind(s);
						return 0;
					}
					tga_comp = sz;
					if (x) *x = tga_w;
					if (y) *y = tga_h;
					if (comp) *comp = tga_comp / 8;
					return 1;                   // seems to have passed everything
				}

				int stbi__tga_test(stbi__context *s)
				{
					int res;
					int sz;
					stbi__get8(s);      //   discard Offset
					sz = stbi__get8(s);   //   color type
					if (sz > 1) return 0;   //   only RGB or indexed allowed
					sz = stbi__get8(s);   //   image type
					if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0;   //   only RGB or grey allowed, +/- RLE
					stbi__get16be(s);      //   discard palette start
					stbi__get16be(s);      //   discard palette length
					stbi__get8(s);         //   discard bits per palette color entry
					stbi__get16be(s);      //   discard x origin
					stbi__get16be(s);      //   discard y origin
					if (stbi__get16be(s) < 1) return 0;      //   test width
					if (stbi__get16be(s) < 1) return 0;      //   test height
					sz = stbi__get8(s);   //   bits per pixel
					if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32))
						res = 0;
					else
						res = 1;
					stbi__rewind(s);
					return res;
				}

				stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					//   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_bits_per_pixel / 8;
					int tga_inverted = stbi__get8(s);
					//   image data
					unsigned char *tga_data;
					unsigned char *tga_palette = NULL;
					int i, j;
					unsigned char raw_data[4];
					int RLE_count = 0;
					int RLE_repeating = 0;
					int read_next_pixel = 1;

					//   do a tiny bit of precessing
					if (tga_image_type >= 8)
					{
						tga_image_type -= 8;
						tga_is_RLE = 1;
					}
					/* int tga_alpha_bits = tga_inverted & 15; */
					tga_inverted = 1 - ((tga_inverted >> 5) & 1);

					//   error check
					if ( //(tga_indexed) ||
						(tga_width < 1) || (tga_height < 1) ||
						(tga_image_type < 1) || (tga_image_type > 3) ||
						((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) &&
						(tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32))
						)
					{
						return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA
					}

					//   If I'm paletted, then I'll use the number of bits from the palette
					if (tga_indexed)
					{
						tga_comp = tga_palette_bits / 8;
					}

					//   tga info
					*x = tga_width;
					*y = tga_height;
					if (comp) *comp = tga_comp;

					tga_data = (unsigned char*)stbi__malloc((size_t)tga_width * tga_height * tga_comp);
					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) {
						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)
						{
							//   any data to skip? (offset usually = 0)
							stbi__skip(s, tga_palette_start);
							//   load the palette
							tga_palette = (unsigned char*)stbi__malloc(tga_palette_len * tga_palette_bits / 8);
							if (!tga_palette) {
								STBI_FREE(tga_data);
								return stbi__errpuc("outofmem", "Out of memory");
							}
							if (!stbi__getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8)) {
								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 1 byte, then perform the lookup
									int pal_idx = stbi__get8(s);
									if (pal_idx >= tga_palette_len)
									{
										//   invalid index
										pal_idx = 0;
									}
									pal_idx *= tga_bits_per_pixel / 8;
									for (j = 0; j * 8 < tga_bits_per_pixel; ++j)
									{
										raw_data[j] = tga_palette[pal_idx + j];
									}
								}
								else
								{
									//   read in the data raw
									for (j = 0; j * 8 < tga_bits_per_pixel; ++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 (tga_comp >= 3)
					{
						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;
					//   OK, done
					return tga_data;
				}
#endif

				// *************************************************************************************************
				// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB

#ifndef STBI_NO_PSD
				int stbi__psd_test(stbi__context *s)
				{
					int r = (stbi__get32be(s) == 0x38425053);
					stbi__rewind(s);
					return r;
				}

				stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					int   pixelCount;
					int channelCount, compression;
					int channel, i, count, len;
					int bitdepth;
					int w, h;
					stbi_uc *out;

					// 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);

					// 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");

					// Create the destination image.
					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 preceeded 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.
								count = 0;
								while (count < pixelCount) {
									len = stbi__get8(s);
									if (len == 128) {
										// No-op.
									}
									else if (len < 128) {
										// Copy next len+1 bytes literally.
										len++;
										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 ^= 0x0FF;
										len += 2;
										val = stbi__get8(s);
										count += len;
										while (len) {
											*p = val;
											p += 4;
											len--;
										}
									}
								}
							}
						}

					}
					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 value for each pixel in the image.

						// Read the data by channel.
						for (channel = 0; channel < 4; channel++) {
							stbi_uc *p;

							p = out + channel;
							if (channel >= channelCount) {
								// Fill this channel with default data.
								stbi_uc val = channel == 3 ? 255 : 0;
								for (i = 0; i < pixelCount; i++, p += 4)
									*p = val;
							}
							else {
								// Read the data.
								if (bitdepth == 16) {
									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);
								}
							}
						}
					}

					if (req_comp && req_comp != 4) {
						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
				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;
				}

				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;

				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;
				}

				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];
				}

				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; y < height; ++y) {
						int packet_idx;

						for (packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
							stbi__pic_packet *packet = &packets[packet_idx];
							stbi_uc *dest = result + y*width * 4;

							switch (packet->type) {
							default:
								return stbi__errpuc("bad format", "packet has bad compression type");

							case 0: {//uncompressed
										int x;

										for (x = 0; x<width; ++x, dest += 4)
										if (!stbi__readval(s, packet->channel, 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; i<count; ++i, dest += 4)
											   stbi__copyval(packet->channel, 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; i<count; ++i, dest += 4)
													stbi__copyval(packet->channel, dest, value);
											}
											else { // Raw
												++count;
												if (count>left) return stbi__errpuc("bad file", "scanline overrun");

												for (i = 0; i < count; ++i, dest += 4)
												if (!stbi__readval(s, packet->channel, dest))
													return 0;
											}
											left -= count;
										}
										break;
							}
							}
						}
					}

					return result;
				}

				stbi_uc *stbi__pic_load(stbi__context *s, int *px, int *py, int *comp, int req_comp)
				{
					stbi_uc *result;
					int i, x, y;

					for (i = 0; i < 92; ++i)
						stbi__get8(s);

					x = stbi__get16be(s);
					y = stbi__get16be(s);
					if (stbi__at_eof(s))  return stbi__errpuc("bad file", "file too short (pic header)");
					if ((1 << 28) / x < y) return stbi__errpuc("too large", "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(x*y * 4);
					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;
				}

				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, *old_out;             // output buffer (always 4 components)
					int flags, bgindex, ratio, transparent, eflags, delay;
					stbi_uc  pal[256][4];
					stbi_uc lpal[256][4];
					stbi__gif_lzw codes[4096];
					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;
				} stbi__gif;

				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;
				}

				int stbi__gif_test(stbi__context *s)
				{
					int r = stbi__gif_test_raw(s);
					stbi__rewind(s);
					return r;
				}

				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;
					}
				}

				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 (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;
				}

				int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
				{
					stbi__gif g;
					if (!stbi__gif_header(s, &g, comp, 1)) {
						stbi__rewind(s);
						return 0;
					}
					if (x) *x = g.w;
					if (y) *y = g.h;
					return 1;
				}

				void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
				{
					stbi_uc *p, *c;

					// 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;

					p = &g->out[g->cur_x + g->cur_y];
					c = &g->color_table[g->codes[code].suffix * 4];

					if (c[3] >= 128) {
						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;
						}
					}
				}

				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 > 4096)        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");
							}
						}
					}
				}

				void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1)
				{
					int x, y;
					stbi_uc *c = g->pal[g->bgindex];
					for (y = y0; y < y1; y += 4 * g->w) {
						for (x = x0; x < x1; x += 4) {
							stbi_uc *p = &g->out[y + x];
							p[0] = c[2];
							p[1] = c[1];
							p[2] = c[0];
							p[3] = 0;
						}
					}
				}

				// this function is designed to support animated gifs, although stb_image doesn't support it
				stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp)
				{
					int i;
					stbi_uc *prev_out = 0;

					if (g->out == 0 && !stbi__gif_header(s, g, comp, 0))
						return 0; // stbi__g_failure_reason set by stbi__gif_header

					prev_out = g->out;
					g->out = (stbi_uc *)stbi__malloc(4 * g->w * g->h);
					if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory");

					switch ((g->eflags & 0x1C) >> 2) {
					case 0: // unspecified (also always used on 1st frame)
						stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h);
						break;
					case 1: // do not dispose
						if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
						g->old_out = prev_out;
						break;
					case 2: // dispose to background
						if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
						stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y);
						break;
					case 3: // dispose to previous
						if (g->old_out) {
							for (i = g->start_y; i < g->max_y; i += 4 * g->w)
								memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x);
						}
						break;
					}

					for (;;) {
						switch (stbi__get8(s)) {
						case 0x2C: /* Image Descriptor */
						{
									   int prev_trans = -1;
									   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;

									   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) {
										   if (g->transparent >= 0 && (g->eflags & 0x01)) {
											   prev_trans = g->pal[g->transparent][3];
											   g->pal[g->transparent][3] = 0;
										   }
										   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 == NULL) return NULL;

									   if (prev_trans != -1)
										   g->pal[g->transparent][3] = (stbi_uc)prev_trans;

									   return o;
						}

						case 0x21: // Comment Extension.
						{
									   int len;
									   if (stbi__get8(s) == 0xF9) { // Graphic Control Extension.
										   len = stbi__get8(s);
										   if (len == 4) {
											   g->eflags = stbi__get8(s);
											   g->delay = stbi__get16le(s);
											   g->transparent = stbi__get8(s);
										   }
										   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");
						}
					}

					STBI_NOTUSED(req_comp);
				}

				stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					stbi_uc *u = 0;
					stbi__gif g;
					memset(&g, 0, sizeof(g));

					u = stbi__gif_load_next(s, &g, comp, req_comp);
					if (u == (stbi_uc *)s) u = 0;  // end of animated gif marker
					if (u) {
						*x = g.w;
						*y = g.h;
						if (req_comp && req_comp != 4)
							u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
					}
					else if (g.out)
						STBI_FREE(g.out);

					return u;
				}

				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
				int stbi__hdr_test_core(stbi__context *s)
				{
					const char *signature = "#?RADIANCE\n";
					int i;
					for (i = 0; signature[i]; ++i)
					if (stbi__get8(s) != signature[i])
						return 0;
					return 1;
				}

				int stbi__hdr_test(stbi__context* s)
				{
					int r = stbi__hdr_test_core(s);
					stbi__rewind(s);
					return r;
				}

#define STBI__HDR_BUFLEN  1024
				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;
				}

				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;
						}
					}
				}

				float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					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;


					// Check identifier
					if (strcmp(stbi__hdr_gettoken(s, buffer), "#?RADIANCE") != 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);

					*x = width;
					*y = height;

					if (comp) *comp = 3;
					if (req_comp == 0) req_comp = 3;

					// Read data
					hdr_data = (float *)stbi__malloc(height * width * req_comp * sizeof(float));

					// 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(width * 4);

							for (k = 0; k < 4; ++k) {
								i = 0;
								while (i < width) {
									count = stbi__get8(s);
									if (count > 128) {
										// Run
										value = stbi__get8(s);
										count -= 128;
										for (z = 0; z < count; ++z)
											scanline[i++ * 4 + k] = value;
									}
									else {
										// Dump
										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);
						}
						STBI_FREE(scanline);
					}

					return hdr_data;
				}

				int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
				{
					char buffer[STBI__HDR_BUFLEN];
					char *token;
					int valid = 0;

					if (strcmp(stbi__hdr_gettoken(s, buffer), "#?RADIANCE") != 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
				int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
				{
					int hsz;
					if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') {
						stbi__rewind(s);
						return 0;
					}
					stbi__skip(s, 12);
					hsz = stbi__get32le(s);
					if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) {
						stbi__rewind(s);
						return 0;
					}
					if (hsz == 12) {
						*x = stbi__get16le(s);
						*y = stbi__get16le(s);
					}
					else {
						*x = stbi__get32le(s);
						*y = stbi__get32le(s);
					}
					if (stbi__get16le(s) != 1) {
						stbi__rewind(s);
						return 0;
					}
					*comp = stbi__get16le(s) / 8;
					return 1;
				}
#endif

#ifndef STBI_NO_PSD
				int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
				{
					int channelCount;
					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);
					if (stbi__get16be(s) != 8) {
						stbi__rewind(s);
						return 0;
					}
					if (stbi__get16be(s) != 3) {
						stbi__rewind(s);
						return 0;
					}
					*comp = 4;
					return 1;
				}
#endif

#ifndef STBI_NO_PIC
				int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
				{
					int act_comp = 0, num_packets = 0, chained;
					stbi__pic_packet packets[10];

					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/m.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)
				//    Does not support 16-bit-per-channel

#ifndef STBI_NO_PNM

				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;
				}

				stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
				{
					stbi_uc *out;
					if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n))
						return 0;
					*x = s->img_x;
					*y = s->img_y;
					*comp = s->img_n;

					out = (stbi_uc *)stbi__malloc(s->img_n * s->img_x * s->img_y);
					if (!out) return stbi__errpuc("outofmem", "Out of memory");
					stbi__getn(s, out, s->img_n * s->img_x * s->img_y);

					if (req_comp && req_comp != s->img_n) {
						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;
				}

				int      stbi__pnm_isspace(char c)
				{
					return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
				}

				void     stbi__pnm_skip_whitespace(stbi__context *s, char *c)
				{
					while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
						*c = (char)stbi__get8(s);
				}

				int      stbi__pnm_isdigit(char c)
				{
					return c >= '0' && c <= '9';
				}

				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);
					}

					return value;
				}

				int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
				{
					int maxv;
					char c, p, t;

					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 .m; '6' is 3-component .ppm

					c = (char)stbi__get8(s);
					stbi__pnm_skip_whitespace(s, &c);

					*x = stbi__pnm_getinteger(s, &c); // read width
					stbi__pnm_skip_whitespace(s, &c);

					*y = stbi__pnm_getinteger(s, &c); // read height
					stbi__pnm_skip_whitespace(s, &c);

					maxv = stbi__pnm_getinteger(s, &c);  // read max value

					if (maxv > 255)
						return stbi__err("max value > 255", "PPM image not 8-bit");
					else
						return 1;
				}
#endif

				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");
				}

#ifndef STBI_NO_STDIO
				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;
				}

				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;
				}
#endif // !STBI_NO_STDIO

				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);
				}

				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);
				}
			} // namespace stbi::decode

			namespace encode
			{
				int stbi_write_png(char const *filename, int w, int h, int comp, const void  *data, int stride_in_bytes);
				int stbi_write_bmp(char const *filename, int w, int h, int comp, const void  *data);
				int stbi_write_tga(char const *filename, int w, int h, int comp, const void  *data);
				int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
				typedef void stbi_write_func(void *context, void *data, int size);
				int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data, int stride_in_bytes);
				int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
				int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
				int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);

#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && defined(STBIW_REALLOC)
				// ok
#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC)
				// ok
#else
#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC."
#endif

#ifndef STBIW_MALLOC
#define STBIW_MALLOC(sz)    malloc(sz)
#define STBIW_REALLOC(p,sz) realloc(p,sz)
#define STBIW_FREE(p)       free(p)
#endif
#ifndef STBIW_MEMMOVE
#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz)
#endif


#ifndef STBIW_ASSERT
#define STBIW_ASSERT(x) assert(x)
#endif

				typedef struct
				{
					stbi_write_func *func;
					void *context;
				} stbi__write_context;

				// initialize a callback-based context
				void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context)
				{
					s->func = c;
					s->context = context;
				}

#ifndef STBI_WRITE_NO_STDIO

				void stbi__stdio_write(void *context, void *data, int size)
				{
					fwrite(data, 1, size, (FILE*)context);
				}

				int stbi__start_write_file(stbi__write_context *s, const char *filename)
				{
					FILE *f = fopen(filename, "wb");
					stbi__start_write_callbacks(s, stbi__stdio_write, (void *)f);
					return f != NULL;
				}

				void stbi__end_write_file(stbi__write_context *s)
				{
					fclose((FILE *)s->context);
				}

#endif // !STBI_WRITE_NO_STDIO

				typedef unsigned int stbiw_uint32;
				typedef int stb_image_write_test[sizeof(stbiw_uint32) == 4 ? 1 : -1];

#ifdef STB_IMAGE_WRITE_
				int stbi_write_tga_with_rle = 1;
#else
				int stbi_write_tga_with_rle = 1;
#endif

				void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v)
				{
					while (*fmt) {
						switch (*fmt++) {
						case ' ': break;
						case '1': { unsigned char x = (unsigned char)va_arg(v, int);
							s->func(s->context, &x, 1);
							break; }
						case '2': { int x = va_arg(v, int);
							unsigned char b[2];
							b[0] = (unsigned char)x;
							b[1] = (unsigned char)(x >> 8);
							s->func(s->context, b, 2);
							break; }
						case '4': { stbiw_uint32 x = va_arg(v, int);
							unsigned char b[4];
							b[0] = (unsigned char)x;
							b[1] = (unsigned char)(x >> 8);
							b[2] = (unsigned char)(x >> 16);
							b[3] = (unsigned char)(x >> 24);
							s->func(s->context, b, 4);
							break; }
						default:
							STBIW_ASSERT(0);
							return;
						}
					}
				}

				void stbiw__writef(stbi__write_context *s, const char *fmt, ...)
				{
					va_list v;
					va_start(v, fmt);
					stbiw__writefv(s, fmt, v);
					va_end(v);
				}

				void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c)
				{
					unsigned char arr[3];
					arr[0] = a, arr[1] = b, arr[2] = c;
					s->func(s->context, arr, 3);
				}

				void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d)
				{
					unsigned char bg[3] = { 255, 0, 255 }, px[3];
					int k;

					if (write_alpha < 0)
						s->func(s->context, &d[comp - 1], 1);

					switch (comp) {
					case 1:
						s->func(s->context, d, 1);
						break;
					case 2:
						if (expand_mono)
							stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp
						else
							s->func(s->context, d, 1);  // monochrome TGA
						break;
					case 4:
						if (!write_alpha) {
							// composite against pink background
							for (k = 0; k < 3; ++k)
								px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255;
							stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]);
							break;
						}
						/* FALLTHROUGH */
					case 3:
						stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]);
						break;
					}
					if (write_alpha > 0)
						s->func(s->context, &d[comp - 1], 1);
				}

				void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
				{
					stbiw_uint32 zero = 0;
					int i, j, j_end;

					if (y <= 0)
						return;

					if (vdir < 0)
						j_end = -1, j = y - 1;
					else
						j_end = y, j = 0;

					for (; j != j_end; j += vdir) {
						for (i = 0; i < x; ++i) {
							unsigned char *d = (unsigned char *)data + (j*x + i)*comp;
							stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d);
						}
						s->func(s->context, &zero, scanline_pad);
					}
				}

				int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...)
				{
					if (y < 0 || x < 0) {
						return 0;
					}
					else {
						va_list v;
						va_start(v, fmt);
						stbiw__writefv(s, fmt, v);
						va_end(v);
						stbiw__write_pixels(s, rgb_dir, vdir, x, y, comp, data, alpha, pad, expand_mono);
						return 1;
					}
				}

				int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data)
				{
					int pad = (-x * 3) & 3;
					return stbiw__outfile(s, -1, -1, x, y, comp, 1, (void *)data, 0, pad,
						"11 4 22 4" "4 44 22 444444",
						'B', 'M', 14 + 40 + (x * 3 + pad)*y, 0, 0, 14 + 40,  // file header
						40, x, y, 1, 24, 0, 0, 0, 0, 0, 0);             // bitmap header
				}

				int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
				{
					stbi__write_context s;
					stbi__start_write_callbacks(&s, func, context);
					return stbi_write_bmp_core(&s, x, y, comp, data);
				}

#ifndef STBI_WRITE_NO_STDIO
				int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
				{
					stbi__write_context s;
					if (stbi__start_write_file(&s, filename)) {
						int r = stbi_write_bmp_core(&s, x, y, comp, data);
						stbi__end_write_file(&s);
						return r;
					}
					else
						return 0;
				}
#endif //!STBI_WRITE_NO_STDIO

				int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data)
				{
					int has_alpha = (comp == 2 || comp == 4);
					int colorbytes = has_alpha ? comp - 1 : comp;
					int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3

					if (y < 0 || x < 0)
						return 0;

					if (!stbi_write_tga_with_rle) {
						return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *)data, has_alpha, 0,
							"111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8);
					}
					else {
						int i, j, k;

						stbiw__writef(s, "111 221 2222 11", 0, 0, format + 8, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8);

						for (j = y - 1; j >= 0; --j) {
							unsigned char *row = (unsigned char *)data + j * x * comp;
							int len;

							for (i = 0; i < x; i += len) {
								unsigned char *begin = row + i * comp;
								int diff = 1;
								len = 1;

								if (i < x - 1) {
									++len;
									diff = memcmp(begin, row + (i + 1) * comp, comp);
									if (diff) {
										const unsigned char *prev = begin;
										for (k = i + 2; k < x && len < 128; ++k) {
											if (memcmp(prev, row + k * comp, comp)) {
												prev += comp;
												++len;
											}
											else {
												--len;
												break;
											}
										}
									}
									else {
										for (k = i + 2; k < x && len < 128; ++k) {
											if (!memcmp(begin, row + k * comp, comp)) {
												++len;
											}
											else {
												break;
											}
										}
									}
								}

								if (diff) {
									unsigned char header = (unsigned char)(len - 1);
									s->func(s->context, &header, 1);
									for (k = 0; k < len; ++k) {
										stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp);
									}
								}
								else {
									unsigned char header = (unsigned char)(len - 129);
									s->func(s->context, &header, 1);
									stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
								}
							}
						}
					}
					return 1;
				}

				int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
				{
					stbi__write_context s;
					stbi__start_write_callbacks(&s, func, context);
					return stbi_write_tga_core(&s, x, y, comp, (void *)data);
				}

#ifndef STBI_WRITE_NO_STDIO
				int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
				{
					stbi__write_context s;
					if (stbi__start_write_file(&s, filename)) {
						int r = stbi_write_tga_core(&s, x, y, comp, (void *)data);
						stbi__end_write_file(&s);
						return r;
					}
					else
						return 0;
				}
#endif

				// *************************************************************************************************
				// Radiance RGBE HDR writer
				// by Baldur Karlsson
#ifndef STBI_WRITE_NO_STDIO

#define stbiw__max(a, b)  ((a) > (b) ? (a) : (b))

				void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
				{
					int exponent;
					float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2]));

					if (maxcomp < 1e-32) {
						rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
					}
					else {
						float normalize = (float)frexp(maxcomp, &exponent) * 256.0f / maxcomp;

						rgbe[0] = (unsigned char)(linear[0] * normalize);
						rgbe[1] = (unsigned char)(linear[1] * normalize);
						rgbe[2] = (unsigned char)(linear[2] * normalize);
						rgbe[3] = (unsigned char)(exponent + 128);
					}
				}

				void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte)
				{
					unsigned char lengthbyte = (unsigned char)(length + 128);
					STBIW_ASSERT(length + 128 <= 255);
					s->func(s->context, &lengthbyte, 1);
					s->func(s->context, &databyte, 1);
				}

				void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data)
				{
					unsigned char lengthbyte = (unsigned char)(length & 0xff);
					STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code
					s->func(s->context, &lengthbyte, 1);
					s->func(s->context, data, length);
				}

				void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline)
				{
					unsigned char scanlineheader[4] = { 2, 2, 0, 0 };
					unsigned char rgbe[4];
					float linear[3];
					int x;

					scanlineheader[2] = (width & 0xff00) >> 8;
					scanlineheader[3] = (width & 0x00ff);

					/* skip RLE for images too small or large */
					if (width < 8 || width >= 32768) {
						for (x = 0; x < width; x++) {
							switch (ncomp) {
							case 4: /* fallthrough */
							case 3: linear[2] = scanline[x*ncomp + 2];
								linear[1] = scanline[x*ncomp + 1];
								linear[0] = scanline[x*ncomp + 0];
								break;
							default:
								linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
								break;
							}
							stbiw__linear_to_rgbe(rgbe, linear);
							s->func(s->context, rgbe, 4);
						}
					}
					else {
						int c, r;
						/* encode into scratch buffer */
						for (x = 0; x < width; x++) {
							switch (ncomp) {
							case 4: /* fallthrough */
							case 3: linear[2] = scanline[x*ncomp + 2];
								linear[1] = scanline[x*ncomp + 1];
								linear[0] = scanline[x*ncomp + 0];
								break;
							default:
								linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
								break;
							}
							stbiw__linear_to_rgbe(rgbe, linear);
							scratch[x + width * 0] = rgbe[0];
							scratch[x + width * 1] = rgbe[1];
							scratch[x + width * 2] = rgbe[2];
							scratch[x + width * 3] = rgbe[3];
						}

						s->func(s->context, scanlineheader, 4);

						/* RLE each component separately */
						for (c = 0; c < 4; c++) {
							unsigned char *comp = &scratch[width*c];

							x = 0;
							while (x < width) {
								// find first run
								r = x;
								while (r + 2 < width) {
									if (comp[r] == comp[r + 1] && comp[r] == comp[r + 2])
										break;
									++r;
								}
								if (r + 2 >= width)
									r = width;
								// dump up to first run
								while (x < r) {
									int len = r - x;
									if (len > 128) len = 128;
									stbiw__write_dump_data(s, len, &comp[x]);
									x += len;
								}
								// if there's a run, output it
								if (r + 2 < width) { // same test as what we break out of in search loop, so only true if we break'd
									// find next byte after run
									while (r < width && comp[r] == comp[x])
										++r;
									// output run up to r
									while (x < r) {
										int len = r - x;
										if (len > 127) len = 127;
										stbiw__write_run_data(s, len, comp[x]);
										x += len;
									}
								}
							}
						}
					}
				}

				int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data)
				{
					if (y <= 0 || x <= 0 || data == NULL)
						return 0;
					else {
						// Each component is stored separately. Allocate scratch space for full output scanline.
						unsigned char *scratch = (unsigned char *)STBIW_MALLOC(x * 4);
						int i, len;
						char buffer[128];
						char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n";
						s->func(s->context, header, sizeof(header)-1);

						len = sprintf(buffer, "EXPOSURE=          1.0000000000000\n\n-Y %d +X %d\n", y, x);
						s->func(s->context, buffer, len);

						for (i = 0; i < y; i++)
							stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x);
						STBIW_FREE(scratch);
						return 1;
					}
				}

				int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data)
				{
					stbi__write_context s;
					stbi__start_write_callbacks(&s, func, context);
					return stbi_write_hdr_core(&s, x, y, comp, (float *)data);
				}

				int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
				{
					stbi__write_context s;
					if (stbi__start_write_file(&s, filename)) {
						int r = stbi_write_hdr_core(&s, x, y, comp, (float *)data);
						stbi__end_write_file(&s);
						return r;
					}
					else
						return 0;
				}
#endif // STBI_WRITE_NO_STDIO


				//////////////////////////////////////////////////////////////////////////////
				//
				// PNG writer
				//

				// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
#define stbiw__sbraw(a) ((int *) (a) - 2)
#define stbiw__sbm(a)   stbiw__sbraw(a)[0]
#define stbiw__sbn(a)   stbiw__sbraw(a)[1]

#define stbiw__sbneedgrow(a,n)  ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a))
#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0)
#define stbiw__sbgrow(a,n)  stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a)))

#define stbiw__sbpush(a, v)      (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v))
#define stbiw__sbcount(a)        ((a) ? stbiw__sbn(a) : 0)
#define stbiw__sbfree(a)         ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0)

				void *stbiw__sbgrowf(void **arr, int increment, int itemsize)
				{
					int m = *arr ? 2 * stbiw__sbm(*arr) + increment : increment + 1;
					void *p = STBIW_REALLOC(*arr ? stbiw__sbraw(*arr) : 0, itemsize * m + sizeof(int)* 2);
					STBIW_ASSERT(p);
					if (p) {
						if (!*arr) ((int *)p)[1] = 0;
						*arr = (void *)((int *)p + 2);
						stbiw__sbm(*arr) = m;
					}
					return *arr;
				}

				unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
				{
					while (*bitcount >= 8) {
						stbiw__sbpush(data, (unsigned char)*bitbuffer);
						*bitbuffer >>= 8;
						*bitcount -= 8;
					}
					return data;
				}

				int stbiw__zlib_bitrev(int code, int codebits)
				{
					int res = 0;
					while (codebits--) {
						res = (res << 1) | (code & 1);
						code >>= 1;
					}
					return res;
				}

				unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit)
				{
					int i;
					for (i = 0; i < limit && i < 258; ++i)
					if (a[i] != b[i]) break;
					return i;
				}

				unsigned int stbiw__zhash(unsigned char *data)
				{
					stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
					hash ^= hash << 3;
					hash += hash >> 5;
					hash ^= hash << 4;
					hash += hash >> 17;
					hash ^= hash << 25;
					hash += hash >> 6;
					return hash;
				}

#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount))
#define stbiw__zlib_add(code,codebits) \
	(bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush())
#define stbiw__zlib_huffa(b,c)  stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c)
				// default huffman tables
#define stbiw__zlib_huff1(n)  stbiw__zlib_huffa(0x30 + (n), 8)
#define stbiw__zlib_huff2(n)  stbiw__zlib_huffa(0x190 + (n)-144, 9)
#define stbiw__zlib_huff3(n)  stbiw__zlib_huffa(0 + (n)-256,7)
#define stbiw__zlib_huff4(n)  stbiw__zlib_huffa(0xc0 + (n)-280,8)
#define stbiw__zlib_huff(n)  ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n))
#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n))

#define stbiw__ZHASH   16384

				unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
				{
					unsigned short lengthc[] = { 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, 259 };
					unsigned char  lengtheb[] = { 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 };
					unsigned short distc[] = { 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, 32768 };
					unsigned char  disteb[] = { 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 };
					unsigned int bitbuf = 0;
					int i, j, bitcount = 0;
					unsigned char *out = NULL;
					unsigned char **hash_table[stbiw__ZHASH]; // 64KB on the stack!
					if (quality < 5) quality = 5;

					stbiw__sbpush(out, 0x78);   // DEFLATE 32K window
					stbiw__sbpush(out, 0x5e);   // FLEVEL = 1
					stbiw__zlib_add(1, 1);  // BFINAL = 1
					stbiw__zlib_add(1, 2);  // BTYPE = 1 -- fixed huffman

					for (i = 0; i < stbiw__ZHASH; ++i)
						hash_table[i] = NULL;

					i = 0;
					while (i < data_len - 3) {
						// hash next 3 bytes of data to be compressed
						int h = stbiw__zhash(data + i)&(stbiw__ZHASH - 1), best = 3;
						unsigned char *bestloc = 0;
						unsigned char **hlist = hash_table[h];
						int n = stbiw__sbcount(hlist);
						for (j = 0; j < n; ++j) {
							if (hlist[j] - data > i - 32768) { // if entry lies within window
								int d = stbiw__zlib_countm(hlist[j], data + i, data_len - i);
								if (d >= best) best = d, bestloc = hlist[j];
							}
						}
						// when hash table entry is too long, delete half the entries
						if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2 * quality) {
							STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, sizeof(hash_table[h][0])*quality);
							stbiw__sbn(hash_table[h]) = quality;
						}
						stbiw__sbpush(hash_table[h], data + i);

						if (bestloc) {
							// "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
							h = stbiw__zhash(data + i + 1)&(stbiw__ZHASH - 1);
							hlist = hash_table[h];
							n = stbiw__sbcount(hlist);
							for (j = 0; j < n; ++j) {
								if (hlist[j] - data > i - 32767) {
									int e = stbiw__zlib_countm(hlist[j], data + i + 1, data_len - i - 1);
									if (e > best) { // if next match is better, bail on current match
										bestloc = NULL;
										break;
									}
								}
							}
						}

						if (bestloc) {
							int d = (int)(data + i - bestloc); // distance back
							STBIW_ASSERT(d <= 32767 && best <= 258);
							for (j = 0; best > lengthc[j + 1] - 1; ++j);
							stbiw__zlib_huff(j + 257);
							if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]);
							for (j = 0; d > distc[j + 1] - 1; ++j);
							stbiw__zlib_add(stbiw__zlib_bitrev(j, 5), 5);
							if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]);
							i += best;
						}
						else {
							stbiw__zlib_huffb(data[i]);
							++i;
						}
					}
					// write out final bytes
					for (; i < data_len; ++i)
						stbiw__zlib_huffb(data[i]);
					stbiw__zlib_huff(256); // end of block
					// pad with 0 bits to byte boundary
					while (bitcount)
						stbiw__zlib_add(0, 1);

					for (i = 0; i < stbiw__ZHASH; ++i)
						(void)stbiw__sbfree(hash_table[i]);

					{
						// compute adler32 on input
						unsigned int k = 0, s1 = 1, s2 = 0;
						int blocklen = (int)(data_len % 5552);
						j = 0;
						while (j < data_len) {
							for (i = 0; i < blocklen; ++i) s1 += data[j + i], s2 += s1;
							s1 %= 65521, s2 %= 65521;
							j += blocklen;
							blocklen = 5552;
						}
						stbiw__sbpush(out, (unsigned char)(s2 >> 8));
						stbiw__sbpush(out, (unsigned char)s2);
						stbiw__sbpush(out, (unsigned char)(s1 >> 8));
						stbiw__sbpush(out, (unsigned char)s1);
					}
					*out_len = stbiw__sbn(out);
					// make returned pointer freeable
					STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len);
					return (unsigned char *)stbiw__sbraw(out);
				}

				unsigned int stbiw__crc32(unsigned char *buffer, int len)
				{
					unsigned int crc_table[256];
					unsigned int crc = ~0u;
					int i, j;
					if (crc_table[1] == 0)
					for (i = 0; i < 256; i++)
					for (crc_table[i] = i, j = 0; j < 8; ++j)
						crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0);
					for (i = 0; i < len; ++i)
						crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
					return ~crc;
				}

#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4)
#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3])

				void stbiw__wpcrc(unsigned char **data, int len)
				{
					unsigned int crc = stbiw__crc32(*data - len - 4, len + 4);
					stbiw__wp32(*data, crc);
				}

				unsigned char stbiw__paeth(int a, int b, int c)
				{
					int p = a + b - c, pa = abs(p - a), pb = abs(p - b), pc = abs(p - c);
					if (pa <= pb && pa <= pc) return (unsigned char)a;
					if (pb <= pc) return (unsigned char)b;
					return (unsigned char)c;
				}

				unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
				{
					int ctype[5] = { -1, 0, 4, 2, 6 };
					unsigned char sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
					unsigned char *out, *o, *filt, *zlib;
					signed char *line_buffer;
					int i, j, k, p, zlen;

					if (stride_bytes == 0)
						stride_bytes = x * n;

					filt = (unsigned char *)STBIW_MALLOC((x*n + 1) * y); if (!filt) return 0;
					line_buffer = (signed char *)STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; }
					for (j = 0; j < y; ++j) {
						int mapping[] = { 0, 1, 2, 3, 4 };
						int firstmap[] = { 0, 1, 0, 5, 6 };
						int *mymap = j ? mapping : firstmap;
						int best = 0, bestval = 0x7fffffff;
						for (p = 0; p < 2; ++p) {
							for (k = p ? best : 0; k < 5; ++k) {
								int type = mymap[k], est = 0;
								unsigned char *z = pixels + stride_bytes*j;
								for (i = 0; i < n; ++i)
									switch (type) {
									case 0: line_buffer[i] = z[i]; break;
									case 1: line_buffer[i] = z[i]; break;
									case 2: line_buffer[i] = z[i] - z[i - stride_bytes]; break;
									case 3: line_buffer[i] = z[i] - (z[i - stride_bytes] >> 1); break;
									case 4: line_buffer[i] = (signed char)(z[i] - stbiw__paeth(0, z[i - stride_bytes], 0)); break;
									case 5: line_buffer[i] = z[i]; break;
									case 6: line_buffer[i] = z[i]; break;
								}
								for (i = n; i < x*n; ++i) {
									switch (type) {
									case 0: line_buffer[i] = z[i]; break;
									case 1: line_buffer[i] = z[i] - z[i - n]; break;
									case 2: line_buffer[i] = z[i] - z[i - stride_bytes]; break;
									case 3: line_buffer[i] = z[i] - ((z[i - n] + z[i - stride_bytes]) >> 1); break;
									case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i - n], z[i - stride_bytes], z[i - stride_bytes - n]); break;
									case 5: line_buffer[i] = z[i] - (z[i - n] >> 1); break;
									case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i - n], 0, 0); break;
									}
								}
								if (p) break;
								for (i = 0; i < x*n; ++i)
									est += abs((signed char)line_buffer[i]);
								if (est < bestval) { bestval = est; best = k; }
							}
						}
						// when we get here, best contains the filter type, and line_buffer contains the data
						filt[j*(x*n + 1)] = (unsigned char)best;
						STBIW_MEMMOVE(filt + j*(x*n + 1) + 1, line_buffer, x*n);
					}
					STBIW_FREE(line_buffer);
					zlib = stbi_zlib_compress(filt, y*(x*n + 1), &zlen, 8); // increase 8 to get smaller but use more memory
					STBIW_FREE(filt);
					if (!zlib) return 0;

					// each tag requires 12 bytes of overhead
					out = (unsigned char *)STBIW_MALLOC(8 + 12 + 13 + 12 + zlen + 12);
					if (!out) return 0;
					*out_len = 8 + 12 + 13 + 12 + zlen + 12;

					o = out;
					STBIW_MEMMOVE(o, sig, 8); o += 8;
					stbiw__wp32(o, 13); // header length
					stbiw__wptag(o, "IHDR");
					stbiw__wp32(o, x);
					stbiw__wp32(o, y);
					*o++ = 8;
					*o++ = (unsigned char)ctype[n];
					*o++ = 0;
					*o++ = 0;
					*o++ = 0;
					stbiw__wpcrc(&o, 13);

					stbiw__wp32(o, zlen);
					stbiw__wptag(o, "IDAT");
					STBIW_MEMMOVE(o, zlib, zlen);
					o += zlen;
					STBIW_FREE(zlib);
					stbiw__wpcrc(&o, zlen);

					stbiw__wp32(o, 0);
					stbiw__wptag(o, "IEND");
					stbiw__wpcrc(&o, 0);

					STBIW_ASSERT(o == out + *out_len);

					return out;
				}

#ifndef STBI_WRITE_NO_STDIO
				int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
				{
					FILE *f;
					int len;
					unsigned char *png = stbi_write_png_to_mem((unsigned char *)data, stride_bytes, x, y, comp, &len);
					if (png == NULL) return 0;
					f = fopen(filename, "wb");
					if (!f) { STBIW_FREE(png); return 0; }
					fwrite(png, 1, len, f);
					fclose(f);
					STBIW_FREE(png);
					return 1;
				}
#endif

				int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
				{
					int len;
					unsigned char *png = stbi_write_png_to_mem((unsigned char *)data, stride_bytes, x, y, comp, &len);
					if (png == NULL) return 0;
					func(context, png, len);
					STBIW_FREE(png);
					return 1;
				}
			} // namespace stbi::encode

			namespace resize
			{
#ifdef _MSC_VER
				typedef unsigned char  stbir_uint8;
				typedef unsigned short stbir_uint16;
				typedef unsigned int   stbir_uint32;
#else
#include <stdint.h>
				typedef uint8_t  stbir_uint8;
				typedef uint16_t stbir_uint16;
				typedef uint32_t stbir_uint32;
#endif

#ifdef STB_IMAGE_RESIZE_STATIC
#define static
#else
#ifdef __cplusplus
#define extern "C"
#else
#define extern
#endif
#endif


				//////////////////////////////////////////////////////////////////////////////
				//
				// Easy-to-use API:
				//
				//     * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4)
				//     * input_w is input image width (x-axis), input_h is input image height (y-axis)
				//     * stride is the offset between successive rows of image data in memory, in bytes. you can
				//       specify 0 to mean packed continuously in memory
				//     * alpha channel is treated identically to other channels.
				//     * colorspace is linear or sRGB as specified by function name
				//     * returned result is 1 for success or 0 in case of an error.
				//       #define STBIR_ASSERT() to trigger an assert on parameter validation errors.
				//     * Memory required grows approximately linearly with input and output size, but with
				//       discontinuities at input_w == output_w and input_h == output_h.
				//     * These functions use a "default" resampling filter defined at compile time. To change the filter,
				//       you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE
				//       and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API.

				int stbir_resize_uint8(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels);

				int stbir_resize_float(const float *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels);


				// The following functions interpret image data as gamma-corrected sRGB.
				// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel,
				// or otherwise provide the index of the alpha channel. Flags value
				// of 0 will probably do the right thing if you're not sure what
				// the flags mean.

#define STBIR_ALPHA_CHANNEL_NONE       -1

				// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will
				// use alpha-weighted resampling (effectively premultiplying, resampling,
				// then unpremultiplying).
#define STBIR_FLAG_ALPHA_PREMULTIPLIED    (1 << 0)
				// The specified alpha channel should be handled as gamma-corrected value even
				// when doing sRGB operations.
#define STBIR_FLAG_ALPHA_USES_COLORSPACE  (1 << 1)

				int stbir_resize_uint8_srgb(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags);


				typedef enum
				{
					STBIR_EDGE_CLAMP = 1,
					STBIR_EDGE_REFLECT = 2,
					STBIR_EDGE_WRAP = 3,
					STBIR_EDGE_ZERO = 4,
				} stbir_edge;

				// This function adds the ability to specify how requests to sample off the edge of the image are handled.
				int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode);

				//////////////////////////////////////////////////////////////////////////////
				//
				// Medium-complexity API
				//
				// This extends the easy-to-use API as follows:
				//
				//     * Alpha-channel can be processed separately
				//       * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE
				//         * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT)
				//         * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)
				//     * Filter can be selected explicitly
				//     * uint16 image type
				//     * sRGB colorspace available for all types
				//     * context parameter for passing to STBIR_MALLOC

				typedef enum
				{
					STBIR_FILTER_DEFAULT = 0,  // use same filter type that easy-to-use API chooses
					STBIR_FILTER_BOX = 1,  // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
					STBIR_FILTER_TRIANGLE = 2,  // On upsampling, produces same results as bilinear texture filtering
					STBIR_FILTER_CUBICBSPLINE = 3,  // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
					STBIR_FILTER_CATMULLROM = 4,  // An interpolating cubic spline
					STBIR_FILTER_MITCHELL = 5,  // Mitchell-Netrevalli filter with B=1/3, C=1/3
				} stbir_filter;

				typedef enum
				{
					STBIR_COLORSPACE_LINEAR,
					STBIR_COLORSPACE_SRGB,

					STBIR_MAX_COLORSPACES,
				} stbir_colorspace;

				// The following functions are all identical except for the type of the image data

				int stbir_resize_uint8_generic(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
					void *alloc_context);

				int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					stbir_uint16 *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
					void *alloc_context);

				int stbir_resize_float_generic(const float *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
					void *alloc_context);



				//////////////////////////////////////////////////////////////////////////////
				//
				// Full-complexity API
				//
				// This extends the medium API as follows:
				//
				//       * uint32 image type
				//     * not typesafe
				//     * separate filter types for each axis
				//     * separate edge modes for each axis
				//     * can specify scale explicitly for subpixel correctness
				//     * can specify image source tile using texture coordinates

				typedef enum
				{
					STBIR_TYPE_UINT8,
					STBIR_TYPE_UINT16,
					STBIR_TYPE_UINT32,
					STBIR_TYPE_FLOAT,

					STBIR_MAX_TYPES
				} stbir_datatype;

				int stbir_resize(const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					stbir_datatype datatype,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
					stbir_filter filter_horizontal, stbir_filter filter_vertical,
					stbir_colorspace space, void *alloc_context);

				int stbir_resize_subpixel(const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					stbir_datatype datatype,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
					stbir_filter filter_horizontal, stbir_filter filter_vertical,
					stbir_colorspace space, void *alloc_context,
					float x_scale, float y_scale,
					float x_offset, float y_offset);

				int stbir_resize_region(const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					stbir_datatype datatype,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
					stbir_filter filter_horizontal, stbir_filter filter_vertical,
					stbir_colorspace space, void *alloc_context,
					float s0, float t0, float s1, float t1);
				// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use.

#ifndef STBIR_ASSERT
//#include <assert.h>
#define STBIR_ASSERT(x) assert(x)
#endif

#ifdef STBIR_DEBUG
#define STBIR__DEBUG_ASSERT STBIR_ASSERT
#else
#define STBIR__DEBUG_ASSERT(x) misc::unused(x)	// fix for clang warning
#endif

				// If you hit this it means I haven't done it yet.
#define STBIR__UNIMPLEMENTED(x) STBIR_ASSERT(!(x))

				// For memset
#include <string.h>

#include <math.h>

#ifndef STBIR_MALLOC
#include <stdlib.h>
#define STBIR_MALLOC(size,c) malloc(size)
#define STBIR_FREE(ptr,c)    free(ptr)
#endif

#ifndef _MSC_VER
#ifdef __cplusplus
#define stbir__inline inline
#else
#define stbir__inline
#endif
#else
#define stbir__inline __forceinline
#endif


				// should produce compiler error if size is wrong
				typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1];

#ifdef _MSC_VER
#define STBIR__NOTUSED(v)  (void)(v)
#else
#define STBIR__NOTUSED(v)  (void)sizeof(v)
#endif

#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0]))

#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE
#define STBIR_DEFAULT_FILTER_UPSAMPLE    STBIR_FILTER_CATMULLROM
#endif

#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE  STBIR_FILTER_MITCHELL
#endif

#ifndef STBIR_PROGRESS_REPORT
#define STBIR_PROGRESS_REPORT(float_0_to_1)
#endif

#ifndef STBIR_MAX_CHANNELS
#define STBIR_MAX_CHANNELS 64
#endif

#if STBIR_MAX_CHANNELS > 65536
#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536."
				// because we store the indices in 16-bit variables
#endif

				// This value is added to alpha just before premultiplication to avoid
				// zeroing out color values. It is equivalent to 2^-80. If you don't want
				// that behavior (it may interfere if you have floating point images with
				// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to
				// disable it.
#ifndef STBIR_ALPHA_EPSILON
#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20))
#endif



#ifdef _MSC_VER
#define STBIR__UNUSED_PARAM(v)  (void)(v)
#else
#define STBIR__UNUSED_PARAM(v)  (void)sizeof(v)
#endif

				// must match stbir_datatype
				unsigned char stbir__type_size[] = {
					1, // STBIR_TYPE_UINT8
					2, // STBIR_TYPE_UINT16
					4, // STBIR_TYPE_UINT32
					4, // STBIR_TYPE_FLOAT
				};

				// Kernel function centered at 0
				typedef float (stbir__kernel_fn)(float x, float scale);
				typedef float (stbir__support_fn)(float scale);

				typedef struct
				{
					stbir__kernel_fn* kernel;
					stbir__support_fn* support;
				} stbir__filter_info;

				// When upsampling, the contributors are which source pixels contribute.
				// When downsampling, the contributors are which destination pixels are contributed to.
				typedef struct
				{
					int n0; // First contributing pixel
					int n1; // Last contributing pixel
				} stbir__contributors;

				typedef struct
				{
					const void* input_data;
					int input_w;
					int input_h;
					int input_stride_bytes;

					void* output_data;
					int output_w;
					int output_h;
					int output_stride_bytes;

					float s0, t0, s1, t1;

					float horizontal_shift; // Units: output pixels
					float vertical_shift;   // Units: output pixels
					float horizontal_scale;
					float vertical_scale;

					int channels;
					int alpha_channel;
					stbir_uint32 flags;
					stbir_datatype type;
					stbir_filter horizontal_filter;
					stbir_filter vertical_filter;
					stbir_edge edge_horizontal;
					stbir_edge edge_vertical;
					stbir_colorspace colorspace;

					stbir__contributors* horizontal_contributors;
					float* horizontal_coefficients;

					stbir__contributors* vertical_contributors;
					float* vertical_coefficients;

					int decode_buffer_pixels;
					float* decode_buffer;

					float* horizontal_buffer;

					// cache these because ceil/floor are inexplicably showing up in profile
					int horizontal_coefficient_width;
					int vertical_coefficient_width;
					int horizontal_filter_pixel_width;
					int vertical_filter_pixel_width;
					int horizontal_filter_pixel_margin;
					int vertical_filter_pixel_margin;
					int horizontal_num_contributors;
					int vertical_num_contributors;

					int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter)
					int ring_buffer_first_scanline;
					int ring_buffer_last_scanline;
					int ring_buffer_begin_index;
					float* ring_buffer;

					float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds.

					int horizontal_contributors_size;
					int horizontal_coefficients_size;
					int vertical_contributors_size;
					int vertical_coefficients_size;
					int decode_buffer_size;
					int horizontal_buffer_size;
					int ring_buffer_size;
					int encode_buffer_size;
				} stbir__info;

				stbir__inline int stbir__min(int a, int b)
				{
					return a < b ? a : b;
				}

				stbir__inline int stbir__max(int a, int b)
				{
					return a > b ? a : b;
				}

				stbir__inline float stbir__saturate(float x)
				{
					if (x < 0)
						return 0;

					if (x > 1)
						return 1;

					return x;
				}

#ifdef STBIR_SATURATE_INT
				stbir__inline stbir_uint8 stbir__saturate8(int x)
				{
					if ((unsigned int)x <= 255)
						return x;

					if (x < 0)
						return 0;

					return 255;
				}

				stbir__inline stbir_uint16 stbir__saturate16(int x)
				{
					if ((unsigned int)x <= 65535)
						return x;

					if (x < 0)
						return 0;

					return 65535;
				}
#endif

				float stbir__srgb_uchar_to_linear_float[256] = {
					0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
					0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f,
					0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f,
					0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f,
					0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f,
					0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f,
					0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f,
					0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
					0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f,
					0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f,
					0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f,
					0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f,
					0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f,
					0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f,
					0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
					0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f,
					0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f,
					0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f,
					0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f,
					0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f,
					0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f,
					0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
					0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f,
					0.982251f, 0.991102f, 1.0f
				};

				float stbir__srgb_to_linear(float f)
				{
					if (f <= 0.04045f)
						return f / 12.92f;
					else
						return (float)pow((f + 0.055f) / 1.055f, 2.4f);
				}

				float stbir__linear_to_srgb(float f)
				{
					if (f <= 0.0031308f)
						return f * 12.92f;
					else
						return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f;
				}

#ifndef STBIR_NON_IEEE_FLOAT
				// From https://gist.github.com/rygorous/2203834

				typedef union
				{
					stbir_uint32 u;
					float f;
				} stbir__FP32;

				const stbir_uint32 fp32_to_srgb8_tab4[104] = {
					0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
					0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
					0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
					0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
					0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
					0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
					0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
					0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
					0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
					0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
					0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
					0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
					0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
				};

				stbir_uint8 stbir__linear_to_srgb_uchar(float in)
				{
					const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps
					const stbir__FP32 minval = { (127 - 13) << 23 };
					stbir_uint32 tab, bias, scale, t;
					stbir__FP32 f;

					// Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively.
					// The tests are carefully written so that NaNs map to 0, same as in the reference
					// implementation.
					if (!(in > minval.f)) // written this way to catch NaNs
						in = minval.f;
					if (in > almostone.f)
						in = almostone.f;

					// Do the table lookup and unpack bias, scale
					f.f = in;
					tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20];
					bias = (tab >> 16) << 9;
					scale = tab & 0xffff;

					// Grab next-highest mantissa bits and perform linear interpolation
					t = (f.u >> 12) & 0xff;
					return (unsigned char)((bias + scale*t) >> 16);
				}

#else
				// sRGB transition values, scaled by 1<<28
				int stbir__srgb_offset_to_linear_scaled[256] =
				{
					0, 40738, 122216, 203693, 285170, 366648, 448125, 529603,
					611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926,
					1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148,
					2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856,
					3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731,
					5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369,
					7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021,
					10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073,
					13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389,
					17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552,
					21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066,
					25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490,
					31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568,
					36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316,
					43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096,
					49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700,
					57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376,
					65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912,
					74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648,
					83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512,
					93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072,
					104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544,
					115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832,
					127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528,
					140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968,
					154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184,
					168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992,
					183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968,
					199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480,
					215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656,
					232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464,
					250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664,
				};

				stbir_uint8 stbir__linear_to_srgb_uchar(float f)
				{
					int x = (int)(f * (1 << 28)); // has headroom so you don't need to clamp
					int v = 0;
					int i;

					// Refine the guess with a short binary search.
					i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
					i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;

					return (stbir_uint8)v;
				}
#endif

				float stbir__filter_trapezoid(float x, float scale)
				{
					float halfscale = scale / 2;
					float t = 0.5f + halfscale;
					STBIR__DEBUG_ASSERT(scale <= 1);

					x = (float)fabs(x);

					if (x >= t)
						return 0;
					else
					{
						float r = 0.5f - halfscale;
						if (x <= r)
							return 1;
						else
							return (t - x) / scale;
					}
				}

				float stbir__support_trapezoid(float scale)
				{
					STBIR__DEBUG_ASSERT(scale <= 1);
					return 0.5f + scale / 2;
				}

				float stbir__filter_triangle(float x, float s)
				{
					STBIR__UNUSED_PARAM(s);

					x = (float)fabs(x);

					if (x <= 1.0f)
						return 1 - x;
					else
						return 0;
				}

				float stbir__filter_cubic(float x, float s)
				{
					STBIR__UNUSED_PARAM(s);

					x = (float)fabs(x);

					if (x < 1.0f)
						return (4 + x*x*(3 * x - 6)) / 6;
					else if (x < 2.0f)
						return (8 + x*(-12 + x*(6 - x))) / 6;

					return (0.0f);
				}

				float stbir__filter_catmullrom(float x, float s)
				{
					STBIR__UNUSED_PARAM(s);

					x = (float)fabs(x);

					if (x < 1.0f)
						return 1 - x*x*(2.5f - 1.5f*x);
					else if (x < 2.0f)
						return 2 - x*(4 + x*(0.5f*x - 2.5f));

					return (0.0f);
				}

				float stbir__filter_mitchell(float x, float s)
				{
					STBIR__UNUSED_PARAM(s);

					x = (float)fabs(x);

					if (x < 1.0f)
						return (16 + x*x*(21 * x - 36)) / 18;
					else if (x < 2.0f)
						return (32 + x*(-60 + x*(36 - 7 * x))) / 18;

					return (0.0f);
				}

				float stbir__support_zero(float s)
				{
					STBIR__UNUSED_PARAM(s);
					return 0;
				}

				float stbir__support_one(float s)
				{
					STBIR__UNUSED_PARAM(s);
					return 1;
				}

				float stbir__support_two(float s)
				{
					STBIR__UNUSED_PARAM(s);
					return 2;
				}

				stbir__filter_info stbir__filter_info_table[] = {
					{ NULL, stbir__support_zero },
					{ stbir__filter_trapezoid, stbir__support_trapezoid },
					{ stbir__filter_triangle, stbir__support_one },
					{ stbir__filter_cubic, stbir__support_two },
					{ stbir__filter_catmullrom, stbir__support_two },
					{ stbir__filter_mitchell, stbir__support_two },
				};

				stbir__inline int stbir__use_upsampling(float ratio)
				{
					return ratio > 1;
				}

				stbir__inline int stbir__use_width_upsampling(stbir__info* stbir_info)
				{
					return stbir__use_upsampling(stbir_info->horizontal_scale);
				}

				stbir__inline int stbir__use_height_upsampling(stbir__info* stbir_info)
				{
					return stbir__use_upsampling(stbir_info->vertical_scale);
				}

				// This is the maximum number of input samples that can affect an output sample
				// with the given filter
				int stbir__get_filter_pixel_width(stbir_filter filter, float scale)
				{
					STBIR_ASSERT(filter != 0);
					STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));

					if (stbir__use_upsampling(scale))
						return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2);
					else
						return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale);
				}

				// This is how much to expand buffers to account for filters seeking outside
				// the image boundaries.
				int stbir__get_filter_pixel_margin(stbir_filter filter, float scale)
				{
					return stbir__get_filter_pixel_width(filter, scale) / 2;
				}

				int stbir__get_coefficient_width(stbir_filter filter, float scale)
				{
					if (stbir__use_upsampling(scale))
						return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2);
					else
						return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2);
				}

				int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size)
				{
					if (stbir__use_upsampling(scale))
						return output_size;
					else
						return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2);
				}

				int stbir__get_total_horizontal_coefficients(stbir__info* info)
				{
					return info->horizontal_num_contributors
						* stbir__get_coefficient_width(info->horizontal_filter, info->horizontal_scale);
				}

				int stbir__get_total_vertical_coefficients(stbir__info* info)
				{
					return info->vertical_num_contributors
						* stbir__get_coefficient_width(info->vertical_filter, info->vertical_scale);
				}

				stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n)
				{
					return &contributors[n];
				}

				// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample,
				// if you change it here change it there too.
				float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c)
				{
					int width = stbir__get_coefficient_width(filter, scale);
					return &coefficients[width*n + c];
				}

				int stbir__edge_wrap_slow(stbir_edge edge, int n, int max)
				{
					switch (edge)
					{
					case STBIR_EDGE_ZERO:
						return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later

					case STBIR_EDGE_CLAMP:
						if (n < 0)
							return 0;

						if (n >= max)
							return max - 1;

						return n; // NOTREACHED

					case STBIR_EDGE_REFLECT:
					{
											   if (n < 0)
											   {
												   if (n < max)
													   return -n;
												   else
													   return max - 1;
											   }

											   if (n >= max)
											   {
												   int max2 = max * 2;
												   if (n >= max2)
													   return 0;
												   else
													   return max2 - n - 1;
											   }

											   return n; // NOTREACHED
					}

					case STBIR_EDGE_WRAP:
						if (n >= 0)
							return (n % max);
						else
						{
							int m = (-n) % max;

							if (m != 0)
								m = max - m;

							return (m);
						}
						return n;  // NOTREACHED

					default:
						STBIR__UNIMPLEMENTED("Unimplemented edge type");
						return 0;
					}
				}

				stbir__inline int stbir__edge_wrap(stbir_edge edge, int n, int max)
				{
					// avoid per-pixel switch
					if (n >= 0 && n < max)
						return n;
					return stbir__edge_wrap_slow(edge, n, max);
				}

				// What input pixels contribute to this output pixel?
				void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out)
				{
					float out_pixel_center = (float)n + 0.5f;
					float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius;
					float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius;

					float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio;
					float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio;

					*in_center_of_out = (out_pixel_center + out_shift) / scale_ratio;
					*in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5));
					*in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5));
				}

				// What output pixels does this input pixel contribute to?
				void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in)
				{
					float in_pixel_center = (float)n + 0.5f;
					float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius;
					float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius;

					float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift;
					float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift;

					*out_center_of_in = in_pixel_center * scale_ratio - out_shift;
					*out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5));
					*out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5));
				}

				void stbir__calculate_coefficients_upsample(stbir__info* stbir_info, stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group)
				{
					int i;
					float total_filter = 0;
					float filter_scale;

					STBIR__DEBUG_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.

					contributor->n0 = in_first_pixel;
					contributor->n1 = in_last_pixel;

					STBIR__DEBUG_ASSERT(contributor->n1 >= contributor->n0);

					for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
					{
						float in_pixel_center = (float)(i + in_first_pixel) + 0.5f;
						coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale);

						// If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.)
						if (i == 0 && !coefficient_group[i])
						{
							contributor->n0 = ++in_first_pixel;
							i--;
							continue;
						}

						total_filter += coefficient_group[i];
					}

					STBIR__DEBUG_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1 / scale) == 0);

					STBIR__DEBUG_ASSERT(total_filter > 0.9);
					STBIR__DEBUG_ASSERT(total_filter < 1.1f); // Make sure it's not way off.

					// Make sure the sum of all coefficients is 1.
					filter_scale = 1 / total_filter;

					for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
						coefficient_group[i] *= filter_scale;

					for (i = in_last_pixel - in_first_pixel; i >= 0; i--)
					{
						if (coefficient_group[i])
							break;

						// This line has no weight. We can skip it.
						contributor->n1 = contributor->n0 + i - 1;
					}
				}

				void stbir__calculate_coefficients_downsample(stbir__info* stbir_info, stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group)
				{
					int i;

					STBIR__DEBUG_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.

					contributor->n0 = out_first_pixel;
					contributor->n1 = out_last_pixel;

					STBIR__DEBUG_ASSERT(contributor->n1 >= contributor->n0);

					for (i = 0; i <= out_last_pixel - out_first_pixel; i++)
					{
						float out_pixel_center = (float)(i + out_first_pixel) + 0.5f;
						float x = out_pixel_center - out_center_of_in;
						coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio;
					}

					STBIR__DEBUG_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0);

					for (i = out_last_pixel - out_first_pixel; i >= 0; i--)
					{
						if (coefficient_group[i])
							break;

						// This line has no weight. We can skip it.
						contributor->n1 = contributor->n0 + i - 1;
					}
				}

				void stbir__normalize_downsample_coefficients(stbir__info* stbir_info, stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size)
				{
					int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
					int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio);
					int i, j;
					int skip;

					for (i = 0; i < output_size; i++)
					{
						float scale;
						float total = 0;

						for (j = 0; j < num_contributors; j++)
						{
							if (i >= contributors[j].n0 && i <= contributors[j].n1)
							{
								float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0);
								total += coefficient;
							}
							else if (i < contributors[j].n0)
								break;
						}

						STBIR__DEBUG_ASSERT(total > 0.9f);
						STBIR__DEBUG_ASSERT(total < 1.1f);

						scale = 1 / total;

						for (j = 0; j < num_contributors; j++)
						{
							if (i >= contributors[j].n0 && i <= contributors[j].n1)
								*stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale;
							else if (i < contributors[j].n0)
								break;
						}
					}

					// Optimize: Skip zero coefficients and contributions outside of image bounds.
					// Do this after normalizing because normalization depends on the n0/n1 values.
					for (j = 0; j < num_contributors; j++)
					{
						int range, max, width;

						skip = 0;
						while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0)
							skip++;

						contributors[j].n0 += skip;

						while (contributors[j].n0 < 0)
						{
							contributors[j].n0++;
							skip++;
						}

						range = contributors[j].n1 - contributors[j].n0 + 1;
						max = stbir__min(num_coefficients, range);

						width = stbir__get_coefficient_width(filter, scale_ratio);
						for (i = 0; i < max; i++)
						{
							if (i + skip >= width)
								break;

							*stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip);
						}

						continue;
					}

					// Using min to avoid writing into invalid pixels.
					for (i = 0; i < num_contributors; i++)
						contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1);
				}

				// Each scan line uses the same kernel values so we should calculate the kernel
				// values once and then we can use them for every scan line.
				void stbir__calculate_filters(stbir__info* stbir_info, stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size)
				{
					int n;
					int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);

					if (stbir__use_upsampling(scale_ratio))
					{
						float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio;

						// Looping through out pixels
						for (n = 0; n < total_contributors; n++)
						{
							float in_center_of_out; // Center of the current out pixel in the in pixel space
							int in_first_pixel, in_last_pixel;

							stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out);

							stbir__calculate_coefficients_upsample(stbir_info, filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
						}
					}
					else
					{
						float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio;

						// Looping through in pixels
						for (n = 0; n < total_contributors; n++)
						{
							float out_center_of_in; // Center of the current out pixel in the in pixel space
							int out_first_pixel, out_last_pixel;
							int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio);

							stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in);

							stbir__calculate_coefficients_downsample(stbir_info, filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
						}

						stbir__normalize_downsample_coefficients(stbir_info, contributors, coefficients, filter, scale_ratio, shift, input_size, output_size);
					}
				}

				float* stbir__get_decode_buffer(stbir__info* stbir_info)
				{
					// The 0 index of the decode buffer starts after the margin. This makes
					// it okay to use negative indexes on the decode buffer.
					return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels];
				}

#define STBIR__DECODE(type, colorspace) ((type) * (STBIR_MAX_COLORSPACES) + (colorspace))

				void stbir__decode_scanline(stbir__info* stbir_info, int n)
				{
					int c;
					int channels = stbir_info->channels;
					int alpha_channel = stbir_info->alpha_channel;
					int type = stbir_info->type;
					int colorspace = stbir_info->colorspace;
					int input_w = stbir_info->input_w;
					int input_stride_bytes = stbir_info->input_stride_bytes;
					float* decode_buffer = stbir__get_decode_buffer(stbir_info);
					stbir_edge edge_horizontal = stbir_info->edge_horizontal;
					stbir_edge edge_vertical = stbir_info->edge_vertical;
					int in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes;
					const void* input_data = (char *)stbir_info->input_data + in_buffer_row_offset;
					int max_x = input_w + stbir_info->horizontal_filter_pixel_margin;
					int decode = STBIR__DECODE(type, colorspace);

					int x = -stbir_info->horizontal_filter_pixel_margin;

					// special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input,
					// and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO
					if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h))
					{
						for (; x < max_x; x++)
						for (c = 0; c < channels; c++)
							decode_buffer[x*channels + c] = 0;
						return;
					}

					switch (decode)
					{
					case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / 255;
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]];

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / 255;
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / 65535;
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / 65535);

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / 65535;
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / 4294967295);
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / 4294967295));

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / 4294967295);
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c];
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
						for (; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;
							int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
							for (c = 0; c < channels; c++)
								decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]);

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel];
						}

						break;

					default:
						STBIR__UNIMPLEMENTED("Unknown type/colorspace/channels combination.");
						break;
					}

					if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED))
					{
						for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++)
						{
							int decode_pixel_index = x * channels;

							// If the alpha value is 0 it will clobber the color values. Make sure it's not.
							float alpha = decode_buffer[decode_pixel_index + alpha_channel];
#ifndef STBIR_NO_ALPHA_EPSILON
							if (stbir_info->type != STBIR_TYPE_FLOAT) {
								alpha += STBIR_ALPHA_EPSILON;
								decode_buffer[decode_pixel_index + alpha_channel] = alpha;
							}
#endif
							for (c = 0; c < channels; c++)
							{
								if (c == alpha_channel)
									continue;

								decode_buffer[decode_pixel_index + c] *= alpha;
							}
						}
					}

					if (edge_horizontal == STBIR_EDGE_ZERO)
					{
						for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++)
						{
							for (c = 0; c < channels; c++)
								decode_buffer[x*channels + c] = 0;
						}
						for (x = input_w; x < max_x; x++)
						{
							for (c = 0; c < channels; c++)
								decode_buffer[x*channels + c] = 0;
						}
					}
				}

				float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length)
				{
					return &ring_buffer[index * ring_buffer_length];
				}

				float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
				{
					int ring_buffer_index;
					float* ring_buffer;

					if (stbir_info->ring_buffer_begin_index < 0)
					{
						ring_buffer_index = stbir_info->ring_buffer_begin_index = 0;
						stbir_info->ring_buffer_first_scanline = n;
					}
					else
					{
						ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline) + 1) % stbir_info->vertical_filter_pixel_width;
						STBIR__DEBUG_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
					}

					ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float));
					memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes);

					stbir_info->ring_buffer_last_scanline = n;

					return ring_buffer;
				}


				void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n, float* output_buffer)
				{
					int x, k;
					int output_w = stbir_info->output_w;
					int kernel_pixel_width = stbir_info->horizontal_filter_pixel_width;
					int channels = stbir_info->channels;
					float* decode_buffer = stbir__get_decode_buffer(stbir_info);
					stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
					float* horizontal_coefficients = stbir_info->horizontal_coefficients;
					int coefficient_width = stbir_info->horizontal_coefficient_width;

					for (x = 0; x < output_w; x++)
					{
						int n0 = horizontal_contributors[x].n0;
						int n1 = horizontal_contributors[x].n1;

						int out_pixel_index = x * channels;
						int coefficient_group = coefficient_width * x;
						int coefficient_counter = 0;

						STBIR__DEBUG_ASSERT(n1 >= n0);
						STBIR__DEBUG_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
						STBIR__DEBUG_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
						STBIR__DEBUG_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
						STBIR__DEBUG_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);

						switch (channels) {
						case 1:
							for (k = n0; k <= n1; k++)
							{
								int in_pixel_index = k * 1;
								float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
							}
							break;
						case 2:
							for (k = n0; k <= n1; k++)
							{
								int in_pixel_index = k * 2;
								float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
								output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
							}
							break;
						case 3:
							for (k = n0; k <= n1; k++)
							{
								int in_pixel_index = k * 3;
								float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
								output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
								output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
							}
							break;
						case 4:
							for (k = n0; k <= n1; k++)
							{
								int in_pixel_index = k * 4;
								float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
								output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
								output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
								output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
							}
							break;
						default:
							for (k = n0; k <= n1; k++)
							{
								int in_pixel_index = k * channels;
								float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
								int c;
								STBIR__DEBUG_ASSERT(coefficient != 0);
								for (c = 0; c < channels; c++)
									output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
							}
							break;
						}
					}
				}

				void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n, float* output_buffer)
				{
					int x, k;
					int input_w = stbir_info->input_w;
					int output_w = stbir_info->output_w;
					int kernel_pixel_width = stbir_info->horizontal_filter_pixel_width;
					int channels = stbir_info->channels;
					float* decode_buffer = stbir__get_decode_buffer(stbir_info);
					stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
					float* horizontal_coefficients = stbir_info->horizontal_coefficients;
					int coefficient_width = stbir_info->horizontal_coefficient_width;
					int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin;
					int max_x = input_w + filter_pixel_margin * 2;

					STBIR__DEBUG_ASSERT(!stbir__use_width_upsampling(stbir_info));

					switch (channels) {
					case 1:
						for (x = 0; x < max_x; x++)
						{
							int n0 = horizontal_contributors[x].n0;
							int n1 = horizontal_contributors[x].n1;

							int in_x = x - filter_pixel_margin;
							int in_pixel_index = in_x * 1;
							int max_n = n1;
							int coefficient_group = coefficient_width * x;

							for (k = n0; k <= max_n; k++)
							{
								int out_pixel_index = k * 1;
								float coefficient = horizontal_coefficients[coefficient_group + k - n0];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
							}
						}
						break;

					case 2:
						for (x = 0; x < max_x; x++)
						{
							int n0 = horizontal_contributors[x].n0;
							int n1 = horizontal_contributors[x].n1;

							int in_x = x - filter_pixel_margin;
							int in_pixel_index = in_x * 2;
							int max_n = n1;
							int coefficient_group = coefficient_width * x;

							for (k = n0; k <= max_n; k++)
							{
								int out_pixel_index = k * 2;
								float coefficient = horizontal_coefficients[coefficient_group + k - n0];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
								output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
							}
						}
						break;

					case 3:
						for (x = 0; x < max_x; x++)
						{
							int n0 = horizontal_contributors[x].n0;
							int n1 = horizontal_contributors[x].n1;

							int in_x = x - filter_pixel_margin;
							int in_pixel_index = in_x * 3;
							int max_n = n1;
							int coefficient_group = coefficient_width * x;

							for (k = n0; k <= max_n; k++)
							{
								int out_pixel_index = k * 3;
								float coefficient = horizontal_coefficients[coefficient_group + k - n0];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
								output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
								output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
							}
						}
						break;

					case 4:
						for (x = 0; x < max_x; x++)
						{
							int n0 = horizontal_contributors[x].n0;
							int n1 = horizontal_contributors[x].n1;

							int in_x = x - filter_pixel_margin;
							int in_pixel_index = in_x * 4;
							int max_n = n1;
							int coefficient_group = coefficient_width * x;

							for (k = n0; k <= max_n; k++)
							{
								int out_pixel_index = k * 4;
								float coefficient = horizontal_coefficients[coefficient_group + k - n0];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
								output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
								output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
								output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
							}
						}
						break;

					default:
						for (x = 0; x < max_x; x++)
						{
							int n0 = horizontal_contributors[x].n0;
							int n1 = horizontal_contributors[x].n1;

							int in_x = x - filter_pixel_margin;
							int in_pixel_index = in_x * channels;
							int max_n = n1;
							int coefficient_group = coefficient_width * x;

							for (k = n0; k <= max_n; k++)
							{
								int c;
								int out_pixel_index = k * channels;
								float coefficient = horizontal_coefficients[coefficient_group + k - n0];
								STBIR__DEBUG_ASSERT(coefficient != 0);
								for (c = 0; c < channels; c++)
									output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
							}
						}
						break;
					}
				}

				void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n)
				{
					// Decode the nth scanline from the source image into the decode buffer.
					stbir__decode_scanline(stbir_info, n);

					// Now resample it into the ring buffer.
					if (stbir__use_width_upsampling(stbir_info))
						stbir__resample_horizontal_upsample(stbir_info, n, stbir__add_empty_ring_buffer_entry(stbir_info, n));
					else
						stbir__resample_horizontal_downsample(stbir_info, n, stbir__add_empty_ring_buffer_entry(stbir_info, n));

					// Now it's sitting in the ring buffer ready to be used as source for the vertical sampling.
				}

				void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n)
				{
					// Decode the nth scanline from the source image into the decode buffer.
					stbir__decode_scanline(stbir_info, n);

					memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float));

					// Now resample it into the horizontal buffer.
					if (stbir__use_width_upsampling(stbir_info))
						stbir__resample_horizontal_upsample(stbir_info, n, stbir_info->horizontal_buffer);
					else
						stbir__resample_horizontal_downsample(stbir_info, n, stbir_info->horizontal_buffer);

					// Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers.
				}

				// Get the specified scan line from the ring buffer.
				float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_size, int ring_buffer_length)
				{
					int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_size;
					return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length);
				}


				void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode)
				{
					int x;
					int n;
					int num_nonalpha;
					stbir_uint16 nonalpha[STBIR_MAX_CHANNELS];

					if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED))
					{
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							float alpha = encode_buffer[pixel_index + alpha_channel];
							float reciprocal_alpha = alpha ? 1.0f / alpha : 0;

							// unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb
							for (n = 0; n < channels; n++)
							if (n != alpha_channel)
								encode_buffer[pixel_index + n] *= reciprocal_alpha;

							// We added in a small epsilon to prevent the color channel from being deleted with zero alpha.
							// Because we only add it for integer types, it will automatically be discarded on integer
							// conversion, so we don't need to subtract it back out (which would be problematic for
							// numeric precision reasons).
						}
					}

					// build a table of all channels that need colorspace correction, so
					// we don't perform colorspace correction on channels that don't need it.
					for (x = 0, num_nonalpha = 0; x < channels; ++x)
					if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
						nonalpha[num_nonalpha++] = x;

#define STBIR__ROUND_INT(f)    ((int)          ((f)+0.5))
#define STBIR__ROUND_UINT(f)   ((stbir_uint32) ((f)+0.5))

#ifdef STBIR__SATURATE_INT
#define STBIR__ENCODE_LINEAR8(f)   stbir__saturate8 (STBIR__ROUND_INT((f) * 255  ))
#define STBIR__ENCODE_LINEAR16(f)  stbir__saturate16(STBIR__ROUND_INT((f) * 65535))
#else
#define STBIR__ENCODE_LINEAR8(f)   (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * 255  )
#define STBIR__ENCODE_LINEAR16(f)  (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * 65535)
#endif

					switch (decode)
					{
					case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < channels; n++)
							{
								int index = pixel_index + n;
								((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]);
							}
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < num_nonalpha; n++)
							{
								int index = pixel_index + nonalpha[n];
								((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]);
							}

							if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
								((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index + alpha_channel]);
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < channels; n++)
							{
								int index = pixel_index + n;
								((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]);
							}
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < num_nonalpha; n++)
							{
								int index = pixel_index + nonalpha[n];
								((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * 65535);
							}

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]);
						}

						break;

					case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < channels; n++)
							{
								int index = pixel_index + n;
								((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * 4294967295);
							}
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < num_nonalpha; n++)
							{
								int index = pixel_index + nonalpha[n];
								((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * 4294967295);
							}

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * 4294967295);
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < channels; n++)
							{
								int index = pixel_index + n;
								((float*)output_buffer)[index] = encode_buffer[index];
							}
						}
						break;

					case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
						for (x = 0; x < num_pixels; ++x)
						{
							int pixel_index = x*channels;

							for (n = 0; n < num_nonalpha; n++)
							{
								int index = pixel_index + nonalpha[n];
								((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]);
							}

							if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
								((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel];
						}
						break;

					default:
						STBIR__UNIMPLEMENTED("Unknown type/colorspace/channels combination.");
						break;
					}
				}

				void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n, int in_first_scanline, int in_last_scanline, float in_center_of_out)
				{
					int x, k;
					int output_w = stbir_info->output_w;
					stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
					float* vertical_coefficients = stbir_info->vertical_coefficients;
					int channels = stbir_info->channels;
					int alpha_channel = stbir_info->alpha_channel;
					int type = stbir_info->type;
					int colorspace = stbir_info->colorspace;
					int kernel_pixel_width = stbir_info->vertical_filter_pixel_width;
					void* output_data = stbir_info->output_data;
					float* encode_buffer = stbir_info->encode_buffer;
					int decode = STBIR__DECODE(type, colorspace);
					int coefficient_width = stbir_info->vertical_coefficient_width;
					int coefficient_counter;
					int contributor = n;

					float* ring_buffer = stbir_info->ring_buffer;
					int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
					int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
					int ring_buffer_last_scanline = stbir_info->ring_buffer_last_scanline;
					int ring_buffer_length = stbir_info->ring_buffer_length_bytes / sizeof(float);

					int n0, n1, output_row_start;
					int coefficient_group = coefficient_width * contributor;

					n0 = vertical_contributors[contributor].n0;
					n1 = vertical_contributors[contributor].n1;

					output_row_start = n * stbir_info->output_stride_bytes;

					STBIR__DEBUG_ASSERT(stbir__use_height_upsampling(stbir_info));

					memset(encode_buffer, 0, output_w * sizeof(float)* channels);

					// I tried reblocking this for better cache usage of encode_buffer
					// (using x_outer, k, x_inner), but it lost speed. -- stb

					coefficient_counter = 0;
					switch (channels) {
					case 1:
						for (k = n0; k <= n1; k++)
						{
							int coefficient_index = coefficient_counter++;
							float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, kernel_pixel_width, ring_buffer_length);
							float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
							for (x = 0; x < output_w; ++x)
							{
								int in_pixel_index = x * 1;
								encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
							}
						}
						break;
					case 2:
						for (k = n0; k <= n1; k++)
						{
							int coefficient_index = coefficient_counter++;
							float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, kernel_pixel_width, ring_buffer_length);
							float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
							for (x = 0; x < output_w; ++x)
							{
								int in_pixel_index = x * 2;
								encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
								encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
							}
						}
						break;
					case 3:
						for (k = n0; k <= n1; k++)
						{
							int coefficient_index = coefficient_counter++;
							float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, kernel_pixel_width, ring_buffer_length);
							float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
							for (x = 0; x < output_w; ++x)
							{
								int in_pixel_index = x * 3;
								encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
								encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
								encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
							}
						}
						break;
					case 4:
						for (k = n0; k <= n1; k++)
						{
							int coefficient_index = coefficient_counter++;
							float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, kernel_pixel_width, ring_buffer_length);
							float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
							for (x = 0; x < output_w; ++x)
							{
								int in_pixel_index = x * 4;
								encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
								encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
								encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
								encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient;
							}
						}
						break;
					default:
						for (k = n0; k <= n1; k++)
						{
							int coefficient_index = coefficient_counter++;
							float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, kernel_pixel_width, ring_buffer_length);
							float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
							for (x = 0; x < output_w; ++x)
							{
								int in_pixel_index = x * channels;
								int c;
								for (c = 0; c < channels; c++)
									encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient;
							}
						}
						break;
					}
					stbir__encode_scanline(stbir_info, output_w, (char *)output_data + output_row_start, encode_buffer, channels, alpha_channel, decode);
				}

				void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n, int in_first_scanline, int in_last_scanline, float in_center_of_out)
				{
					int x, k;
					int output_w = stbir_info->output_w;
					int output_h = stbir_info->output_h;
					stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
					float* vertical_coefficients = stbir_info->vertical_coefficients;
					int channels = stbir_info->channels;
					int kernel_pixel_width = stbir_info->vertical_filter_pixel_width;
					void* output_data = stbir_info->output_data;
					float* horizontal_buffer = stbir_info->horizontal_buffer;
					int coefficient_width = stbir_info->vertical_coefficient_width;
					int contributor = n + stbir_info->vertical_filter_pixel_margin;

					float* ring_buffer = stbir_info->ring_buffer;
					int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
					int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
					int ring_buffer_last_scanline = stbir_info->ring_buffer_last_scanline;
					int ring_buffer_length = stbir_info->ring_buffer_length_bytes / sizeof(float);
					int n0, n1;

					n0 = vertical_contributors[contributor].n0;
					n1 = vertical_contributors[contributor].n1;

					STBIR__DEBUG_ASSERT(!stbir__use_height_upsampling(stbir_info));

					for (k = n0; k <= n1; k++)
					{
						int coefficient_index = k - n0;
						int coefficient_group = coefficient_width * contributor;
						float coefficient = vertical_coefficients[coefficient_group + coefficient_index];

						float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, kernel_pixel_width, ring_buffer_length);

						switch (channels) {
						case 1:
							for (x = 0; x < output_w; x++)
							{
								int in_pixel_index = x * 1;
								ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
							}
							break;
						case 2:
							for (x = 0; x < output_w; x++)
							{
								int in_pixel_index = x * 2;
								ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
								ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
							}
							break;
						case 3:
							for (x = 0; x < output_w; x++)
							{
								int in_pixel_index = x * 3;
								ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
								ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
								ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
							}
							break;
						case 4:
							for (x = 0; x < output_w; x++)
							{
								int in_pixel_index = x * 4;
								ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
								ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
								ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
								ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient;
							}
							break;
						default:
							for (x = 0; x < output_w; x++)
							{
								int in_pixel_index = x * channels;

								int c;
								for (c = 0; c < channels; c++)
									ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient;
							}
							break;
						}
					}
				}

				void stbir__buffer_loop_upsample(stbir__info* stbir_info)
				{
					int y;
					float scale_ratio = stbir_info->vertical_scale;
					float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1 / scale_ratio) * scale_ratio;

					STBIR__DEBUG_ASSERT(stbir__use_height_upsampling(stbir_info));

					for (y = 0; y < stbir_info->output_h; y++)
					{
						float in_center_of_out = 0; // Center of the current out scanline in the in scanline space
						int in_first_scanline = 0, in_last_scanline = 0;

						stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out);

						STBIR__DEBUG_ASSERT(in_last_scanline - in_first_scanline <= stbir_info->vertical_filter_pixel_width);

						if (stbir_info->ring_buffer_begin_index >= 0)
						{
							// Get rid of whatever we don't need anymore.
							while (in_first_scanline > stbir_info->ring_buffer_first_scanline)
							{
								if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
								{
									// We just popped the last scanline off the ring buffer.
									// Reset it to the empty state.
									stbir_info->ring_buffer_begin_index = -1;
									stbir_info->ring_buffer_first_scanline = 0;
									stbir_info->ring_buffer_last_scanline = 0;
									break;
								}
								else
								{
									stbir_info->ring_buffer_first_scanline++;
									stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->vertical_filter_pixel_width;
								}
							}
						}

						// Load in new ones.
						if (stbir_info->ring_buffer_begin_index < 0)
							stbir__decode_and_resample_upsample(stbir_info, in_first_scanline);

						while (in_last_scanline > stbir_info->ring_buffer_last_scanline)
							stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1);

						// Now all buffers should be ready to write a row of vertical sampling.
						stbir__resample_vertical_upsample(stbir_info, y, in_first_scanline, in_last_scanline, in_center_of_out);

						STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h);
					}
				}

				void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline)
				{
					int output_stride_bytes = stbir_info->output_stride_bytes;
					int channels = stbir_info->channels;
					int alpha_channel = stbir_info->alpha_channel;
					int type = stbir_info->type;
					int colorspace = stbir_info->colorspace;
					int output_w = stbir_info->output_w;
					void* output_data = stbir_info->output_data;
					int decode = STBIR__DECODE(type, colorspace);

					float* ring_buffer = stbir_info->ring_buffer;
					int ring_buffer_length = stbir_info->ring_buffer_length_bytes / sizeof(float);

					if (stbir_info->ring_buffer_begin_index >= 0)
					{
						// Get rid of whatever we don't need anymore.
						while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline)
						{
							if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h)
							{
								int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes;
								float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length);
								stbir__encode_scanline(stbir_info, output_w, (char *)output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode);
								STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h);
							}

							if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
							{
								// We just popped the last scanline off the ring buffer.
								// Reset it to the empty state.
								stbir_info->ring_buffer_begin_index = -1;
								stbir_info->ring_buffer_first_scanline = 0;
								stbir_info->ring_buffer_last_scanline = 0;
								break;
							}
							else
							{
								stbir_info->ring_buffer_first_scanline++;
								stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->vertical_filter_pixel_width;
							}
						}
					}
				}

				void stbir__buffer_loop_downsample(stbir__info* stbir_info)
				{
					int y;
					float scale_ratio = stbir_info->vertical_scale;
					int output_h = stbir_info->output_h;
					float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio;
					int pixel_margin = stbir_info->vertical_filter_pixel_margin;
					int max_y = stbir_info->input_h + pixel_margin;

					STBIR__DEBUG_ASSERT(!stbir__use_height_upsampling(stbir_info));

					for (y = -pixel_margin; y < max_y; y++)
					{
						float out_center_of_in; // Center of the current out scanline in the in scanline space
						int out_first_scanline, out_last_scanline;

						stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in);

						STBIR__DEBUG_ASSERT(out_last_scanline - out_first_scanline <= stbir_info->vertical_filter_pixel_width);

						if (out_last_scanline < 0 || out_first_scanline >= output_h)
							continue;

						stbir__empty_ring_buffer(stbir_info, out_first_scanline);

						stbir__decode_and_resample_downsample(stbir_info, y);

						// Load in new ones.
						if (stbir_info->ring_buffer_begin_index < 0)
							stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline);

						while (out_last_scanline > stbir_info->ring_buffer_last_scanline)
							stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1);

						// Now the horizontal buffer is ready to write to all ring buffer rows.
						stbir__resample_vertical_downsample(stbir_info, y, out_first_scanline, out_last_scanline, out_center_of_in);
					}

					stbir__empty_ring_buffer(stbir_info, stbir_info->output_h);
				}

				void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels)
				{
					info->input_w = input_w;
					info->input_h = input_h;
					info->output_w = output_w;
					info->output_h = output_h;
					info->channels = channels;
				}

				void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform)
				{
					info->s0 = s0;
					info->t0 = t0;
					info->s1 = s1;
					info->t1 = t1;

					if (transform)
					{
						info->horizontal_scale = transform[0];
						info->vertical_scale = transform[1];
						info->horizontal_shift = transform[2];
						info->vertical_shift = transform[3];
					}
					else
					{
						info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0);
						info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0);

						info->horizontal_shift = s0 * info->input_w / (s1 - s0);
						info->vertical_shift = t0 * info->input_h / (t1 - t0);
					}
				}

				void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter)
				{
					if (h_filter == 0)
						h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
					if (v_filter == 0)
						v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
					info->horizontal_filter = h_filter;
					info->vertical_filter = v_filter;
				}

				stbir_uint32 stbir__calculate_memory(stbir__info *info)
				{
					int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
					int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale);

					info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w);
					info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale, info->vertical_filter, info->input_h, info->output_h);

					info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors);
					info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float);
					info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors);
					info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float);
					info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float);
					info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float);
					info->ring_buffer_size = info->output_w * info->channels * filter_height * sizeof(float);
					info->encode_buffer_size = info->output_w * info->channels * sizeof(float);

					STBIR_ASSERT(info->horizontal_filter != 0);
					STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
					STBIR_ASSERT(info->vertical_filter != 0);
					STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late

					if (stbir__use_height_upsampling(info))
						// The horizontal buffer is for when we're downsampling the height and we
						// can't output the result of sampling the decode buffer directly into the
						// ring buffers.
						info->horizontal_buffer_size = 0;
					else
						// The encode buffer is to retain precision in the height upsampling method
						// and isn't used when height downsampling.
						info->encode_buffer_size = 0;

					return info->horizontal_contributors_size + info->horizontal_coefficients_size
						+ info->vertical_contributors_size + info->vertical_coefficients_size
						+ info->decode_buffer_size + info->horizontal_buffer_size
						+ info->ring_buffer_size + info->encode_buffer_size;
				}

				int stbir__resize_allocated(stbir__info *info,
					const void* input_data, int input_stride_in_bytes,
					void* output_data, int output_stride_in_bytes,
					int alpha_channel, stbir_uint32 flags, stbir_datatype type,
					stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace,
					void* tempmem, size_t tempmem_size_in_bytes)
				{
					size_t memory_required = stbir__calculate_memory(info);

					int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type];
					int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type];

#ifdef STBIR_DEBUG_OVERWRITE_TEST
#define OVERWRITE_ARRAY_SIZE 8
					unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE];
					unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE];
					unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE];
					unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE];

					size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type];
					memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
					memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE);
					memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
					memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE);
#endif

					STBIR_ASSERT(info->channels >= 0);
					STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS);

					if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS)
						return 0;

					STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
					STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));

					if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
						return 0;
					if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
						return 0;

					if (alpha_channel < 0)
						flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED;

					if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED))
						STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels);

					if (alpha_channel >= info->channels)
						return 0;

					STBIR_ASSERT(tempmem);

					if (!tempmem)
						return 0;

					STBIR_ASSERT(tempmem_size_in_bytes >= memory_required);

					if (tempmem_size_in_bytes < memory_required)
						return 0;

					memset(tempmem, 0, tempmem_size_in_bytes);

					info->input_data = input_data;
					info->input_stride_bytes = width_stride_input;

					info->output_data = output_data;
					info->output_stride_bytes = width_stride_output;

					info->alpha_channel = alpha_channel;
					info->flags = flags;
					info->type = type;
					info->edge_horizontal = edge_horizontal;
					info->edge_vertical = edge_vertical;
					info->colorspace = colorspace;

					info->horizontal_coefficient_width = stbir__get_coefficient_width(info->horizontal_filter, info->horizontal_scale);
					info->vertical_coefficient_width = stbir__get_coefficient_width(info->vertical_filter, info->vertical_scale);
					info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width(info->horizontal_filter, info->horizontal_scale);
					info->vertical_filter_pixel_width = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale);
					info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
					info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter, info->vertical_scale);

					info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float);
					info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2;

#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size)

					info->horizontal_contributors = (stbir__contributors *)tempmem;
					info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float);
					info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors);
					info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float);
					info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float);

					if (stbir__use_height_upsampling(info))
					{
						info->horizontal_buffer = NULL;
						info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
						info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float);

						STBIR__DEBUG_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
					}
					else
					{
						info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
						info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float);
						info->encode_buffer = NULL;

						STBIR__DEBUG_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
					}

#undef STBIR__NEXT_MEMPTR

					// This signals that the ring buffer is empty
					info->ring_buffer_begin_index = -1;

					stbir__calculate_filters(info, info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w);
					stbir__calculate_filters(info, info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h);

					STBIR_PROGRESS_REPORT(0);

					if (stbir__use_height_upsampling(info))
						stbir__buffer_loop_upsample(info);
					else
						stbir__buffer_loop_downsample(info);

					STBIR_PROGRESS_REPORT(1);

#ifdef STBIR_DEBUG_OVERWRITE_TEST
					STBIR__DEBUG_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
					STBIR__DEBUG_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
					STBIR__DEBUG_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
					STBIR__DEBUG_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
#endif

					return 1;
				}


				int stbir__resize_arbitrary(
					void *alloc_context,
					const void* input_data, int input_w, int input_h, int input_stride_in_bytes,
					void* output_data, int output_w, int output_h, int output_stride_in_bytes,
					float s0, float t0, float s1, float t1, float *transform,
					int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type,
					stbir_filter h_filter, stbir_filter v_filter,
					stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace)
				{
					stbir__info info;
					int result;
					size_t memory_required;
					void* extra_memory;

					stbir__setup(&info, input_w, input_h, output_w, output_h, channels);
					stbir__calculate_transform(&info, s0, t0, s1, t1, transform);
					stbir__choose_filter(&info, h_filter, v_filter);
					memory_required = stbir__calculate_memory(&info);
					extra_memory = STBIR_MALLOC(memory_required, alloc_context);

					if (!extra_memory)
						return 0;

					result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes,
						output_data, output_stride_in_bytes,
						alpha_channel, flags, type,
						edge_horizontal, edge_vertical,
						colorspace, extra_memory, memory_required);

					STBIR_FREE(extra_memory, alloc_context);

					return result;
				}

				int stbir_resize_uint8(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels)
				{
					return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, -1, 0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
						STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
				}

				int stbir_resize_float(const float *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels)
				{
					return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, -1, 0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
						STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
				}

				int stbir_resize_uint8_srgb(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags)
				{
					return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, alpha_channel, flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
						STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB);
				}

				int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode)
				{
					return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, alpha_channel, flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
						edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB);
				}

				int stbir_resize_uint8_generic(const unsigned char *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
					void *alloc_context)
				{
					return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, alpha_channel, flags, STBIR_TYPE_UINT8, filter, filter,
						edge_wrap_mode, edge_wrap_mode, space);
				}

				int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					stbir_uint16 *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
					void *alloc_context)
				{
					return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, alpha_channel, flags, STBIR_TYPE_UINT16, filter, filter,
						edge_wrap_mode, edge_wrap_mode, space);
				}


				int stbir_resize_float_generic(const float *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
					void *alloc_context)
				{
					return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, alpha_channel, flags, STBIR_TYPE_FLOAT, filter, filter,
						edge_wrap_mode, edge_wrap_mode, space);
				}


				int stbir_resize(const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					stbir_datatype datatype,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
					stbir_filter filter_horizontal, stbir_filter filter_vertical,
					stbir_colorspace space, void *alloc_context)
				{
					return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, NULL, num_channels, alpha_channel, flags, datatype, filter_horizontal, filter_vertical,
						edge_mode_horizontal, edge_mode_vertical, space);
				}


				int stbir_resize_subpixel(const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					stbir_datatype datatype,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
					stbir_filter filter_horizontal, stbir_filter filter_vertical,
					stbir_colorspace space, void *alloc_context,
					float x_scale, float y_scale,
					float x_offset, float y_offset)
				{
					float transform[4];
					transform[0] = x_scale;
					transform[1] = y_scale;
					transform[2] = x_offset;
					transform[3] = y_offset;
					return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						0, 0, 1, 1, transform, num_channels, alpha_channel, flags, datatype, filter_horizontal, filter_vertical,
						edge_mode_horizontal, edge_mode_vertical, space);
				}

				int stbir_resize_region(const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes,
					void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
					stbir_datatype datatype,
					int num_channels, int alpha_channel, int flags,
					stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
					stbir_filter filter_horizontal, stbir_filter filter_vertical,
					stbir_colorspace space, void *alloc_context,
					float s0, float t0, float s1, float t1)
				{
					return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
						output_pixels, output_w, output_h, output_stride_in_bytes,
						s0, t0, s1, t1, NULL, num_channels, alpha_channel, flags, datatype, filter_horizontal, filter_vertical,
						edge_mode_horizontal, edge_mode_vertical, space);
				}
			} // namespace stbi::resize

		} // namespace stbi

		namespace jo
		{
			const unsigned char s_jo_ZigZag[] = { 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63 };

			void jo_writeBits(FILE *fp, int &bitBuf, int &bitCnt, const unsigned short *bs) {
				bitCnt += bs[1];
				bitBuf |= bs[0] << (24 - bitCnt);
				while (bitCnt >= 8) {
					unsigned char c = (bitBuf >> 16) & 255;
					putc(c, fp);
					if (c == 255) {
						putc(0, fp);
					}
					bitBuf <<= 8;
					bitCnt -= 8;
				}
			}

			void jo_DCT(float &d0, float &d1, float &d2, float &d3, float &d4, float &d5, float &d6, float &d7) {
				float tmp0 = d0 + d7;
				float tmp7 = d0 - d7;
				float tmp1 = d1 + d6;
				float tmp6 = d1 - d6;
				float tmp2 = d2 + d5;
				float tmp5 = d2 - d5;
				float tmp3 = d3 + d4;
				float tmp4 = d3 - d4;

				// Even part
				float tmp10 = tmp0 + tmp3;	// phase 2
				float tmp13 = tmp0 - tmp3;
				float tmp11 = tmp1 + tmp2;
				float tmp12 = tmp1 - tmp2;

				d0 = tmp10 + tmp11; 		// phase 3
				d4 = tmp10 - tmp11;

				float z1 = (tmp12 + tmp13) * 0.707106781f; // c4
				d2 = tmp13 + z1; 		// phase 5
				d6 = tmp13 - z1;

				// Odd part
				tmp10 = tmp4 + tmp5; 		// phase 2
				tmp11 = tmp5 + tmp6;
				tmp12 = tmp6 + tmp7;

				// The rotator is modified from fig 4-8 to avoid extra negations.
				float z5 = (tmp10 - tmp12) * 0.382683433f; // c6
				float z2 = tmp10 * 0.541196100f + z5; // c2-c6
				float z4 = tmp12 * 1.306562965f + z5; // c2+c6
				float z3 = tmp11 * 0.707106781f; // c4

				float z11 = tmp7 + z3;		// phase 5
				float z13 = tmp7 - z3;

				d5 = z13 + z2;			// phase 6
				d3 = z13 - z2;
				d1 = z11 + z4;
				d7 = z11 - z4;
			}

			void jo_calcBits(int val, unsigned short bits[2]) {
				int tmp1 = val < 0 ? -val : val;
				val = val < 0 ? val - 1 : val;
				bits[1] = 1;
				while (tmp1 >>= 1) {
					++bits[1];
				}
				bits[0] = val & ((1 << bits[1]) - 1);
			}

			int jo_processDU(FILE *fp, int &bitBuf, int &bitCnt, float *CDU, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
				const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
				const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };

				// DCT rows
				for (int dataOff = 0; dataOff<64; dataOff += 8) {
					jo_DCT(CDU[dataOff], CDU[dataOff + 1], CDU[dataOff + 2], CDU[dataOff + 3], CDU[dataOff + 4], CDU[dataOff + 5], CDU[dataOff + 6], CDU[dataOff + 7]);
				}
				// DCT columns
				for (int dataOff = 0; dataOff<8; ++dataOff) {
					jo_DCT(CDU[dataOff], CDU[dataOff + 8], CDU[dataOff + 16], CDU[dataOff + 24], CDU[dataOff + 32], CDU[dataOff + 40], CDU[dataOff + 48], CDU[dataOff + 56]);
				}
				// Quantize/descale/zigzag the coefficients
				int DU[64];
				for (int i = 0; i<64; ++i) {
					float v = CDU[i] * fdtbl[i];
					DU[s_jo_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
				}

				// Encode DC
				int diff = DU[0] - DC;
				if (diff == 0) {
					jo_writeBits(fp, bitBuf, bitCnt, HTDC[0]);
				}
				else {
					unsigned short bits[2];
					jo_calcBits(diff, bits);
					jo_writeBits(fp, bitBuf, bitCnt, HTDC[bits[1]]);
					jo_writeBits(fp, bitBuf, bitCnt, bits);
				}
				// Encode ACs
				int end0pos = 63;
				for (; (end0pos>0) && (DU[end0pos] == 0); --end0pos) {
				}
				// end0pos = first element in reverse order !=0
				if (end0pos == 0) {
					jo_writeBits(fp, bitBuf, bitCnt, EOB);
					return DU[0];
				}
				for (int i = 1; i <= end0pos; ++i) {
					int startpos = i;
					for (; DU[i] == 0 && i <= end0pos; ++i) {
					}
					int nrzeroes = i - startpos;
					if (nrzeroes >= 16) {
						int lng = nrzeroes >> 4;
						for (int nrmarker = 1; nrmarker <= lng; ++nrmarker)
							jo_writeBits(fp, bitBuf, bitCnt, M16zeroes);
						nrzeroes &= 15;
					}
					unsigned short bits[2];
					jo_calcBits(DU[i], bits);
					jo_writeBits(fp, bitBuf, bitCnt, HTAC[(nrzeroes << 4) + bits[1]]);
					jo_writeBits(fp, bitBuf, bitCnt, bits);
				}
				if (end0pos != 63) {
					jo_writeBits(fp, bitBuf, bitCnt, EOB);
				}
				return DU[0];
			}

			bool jo_write_jpg(const char *filename, const void *data, int width, int height, int comp, int quality) {
				// Constants that don't pollute global namespace
				const unsigned char std_dc_luminance_nrcodes[] = { 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 };
				const unsigned char std_dc_luminance_values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
				const unsigned char std_ac_luminance_nrcodes[] = { 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d };
				const unsigned char std_ac_luminance_values[] = {
					0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
					0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
					0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
					0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
					0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
					0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
					0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
				};
				const unsigned char std_dc_chrominance_nrcodes[] = { 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 };
				const unsigned char std_dc_chrominance_values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
				const unsigned char std_ac_chrominance_nrcodes[] = { 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 };
				const unsigned char std_ac_chrominance_values[] = {
					0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
					0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
					0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
					0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
					0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
					0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
					0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
				};
				// Huffman tables
				const unsigned short YDC_HT[256][2] = { { 0, 2 }, { 2, 3 }, { 3, 3 }, { 4, 3 }, { 5, 3 }, { 6, 3 }, { 14, 4 }, { 30, 5 }, { 62, 6 }, { 126, 7 }, { 254, 8 }, { 510, 9 } };
				const unsigned short UVDC_HT[256][2] = { { 0, 2 }, { 1, 2 }, { 2, 2 }, { 6, 3 }, { 14, 4 }, { 30, 5 }, { 62, 6 }, { 126, 7 }, { 254, 8 }, { 510, 9 }, { 1022, 10 }, { 2046, 11 } };
				const unsigned short YAC_HT[256][2] = {
					{ 10, 4 }, { 0, 2 }, { 1, 2 }, { 4, 3 }, { 11, 4 }, { 26, 5 }, { 120, 7 }, { 248, 8 }, { 1014, 10 }, { 65410, 16 }, { 65411, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 12, 4 }, { 27, 5 }, { 121, 7 }, { 502, 9 }, { 2038, 11 }, { 65412, 16 }, { 65413, 16 }, { 65414, 16 }, { 65415, 16 }, { 65416, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 28, 5 }, { 249, 8 }, { 1015, 10 }, { 4084, 12 }, { 65417, 16 }, { 65418, 16 }, { 65419, 16 }, { 65420, 16 }, { 65421, 16 }, { 65422, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 58, 6 }, { 503, 9 }, { 4085, 12 }, { 65423, 16 }, { 65424, 16 }, { 65425, 16 }, { 65426, 16 }, { 65427, 16 }, { 65428, 16 }, { 65429, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 59, 6 }, { 1016, 10 }, { 65430, 16 }, { 65431, 16 }, { 65432, 16 }, { 65433, 16 }, { 65434, 16 }, { 65435, 16 }, { 65436, 16 }, { 65437, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 122, 7 }, { 2039, 11 }, { 65438, 16 }, { 65439, 16 }, { 65440, 16 }, { 65441, 16 }, { 65442, 16 }, { 65443, 16 }, { 65444, 16 }, { 65445, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 123, 7 }, { 4086, 12 }, { 65446, 16 }, { 65447, 16 }, { 65448, 16 }, { 65449, 16 }, { 65450, 16 }, { 65451, 16 }, { 65452, 16 }, { 65453, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 250, 8 }, { 4087, 12 }, { 65454, 16 }, { 65455, 16 }, { 65456, 16 }, { 65457, 16 }, { 65458, 16 }, { 65459, 16 }, { 65460, 16 }, { 65461, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 504, 9 }, { 32704, 15 }, { 65462, 16 }, { 65463, 16 }, { 65464, 16 }, { 65465, 16 }, { 65466, 16 }, { 65467, 16 }, { 65468, 16 }, { 65469, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 505, 9 }, { 65470, 16 }, { 65471, 16 }, { 65472, 16 }, { 65473, 16 }, { 65474, 16 }, { 65475, 16 }, { 65476, 16 }, { 65477, 16 }, { 65478, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 506, 9 }, { 65479, 16 }, { 65480, 16 }, { 65481, 16 }, { 65482, 16 }, { 65483, 16 }, { 65484, 16 }, { 65485, 16 }, { 65486, 16 }, { 65487, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 1017, 10 }, { 65488, 16 }, { 65489, 16 }, { 65490, 16 }, { 65491, 16 }, { 65492, 16 }, { 65493, 16 }, { 65494, 16 }, { 65495, 16 }, { 65496, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 1018, 10 }, { 65497, 16 }, { 65498, 16 }, { 65499, 16 }, { 65500, 16 }, { 65501, 16 }, { 65502, 16 }, { 65503, 16 }, { 65504, 16 }, { 65505, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 2040, 11 }, { 65506, 16 }, { 65507, 16 }, { 65508, 16 }, { 65509, 16 }, { 65510, 16 }, { 65511, 16 }, { 65512, 16 }, { 65513, 16 }, { 65514, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 65515, 16 }, { 65516, 16 }, { 65517, 16 }, { 65518, 16 }, { 65519, 16 }, { 65520, 16 }, { 65521, 16 }, { 65522, 16 }, { 65523, 16 }, { 65524, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 2041, 11 }, { 65525, 16 }, { 65526, 16 }, { 65527, 16 }, { 65528, 16 }, { 65529, 16 }, { 65530, 16 }, { 65531, 16 }, { 65532, 16 }, { 65533, 16 }, { 65534, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }
				};
				const unsigned short UVAC_HT[256][2] = {
					{ 0, 2 }, { 1, 2 }, { 4, 3 }, { 10, 4 }, { 24, 5 }, { 25, 5 }, { 56, 6 }, { 120, 7 }, { 500, 9 }, { 1014, 10 }, { 4084, 12 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 11, 4 }, { 57, 6 }, { 246, 8 }, { 501, 9 }, { 2038, 11 }, { 4085, 12 }, { 65416, 16 }, { 65417, 16 }, { 65418, 16 }, { 65419, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 26, 5 }, { 247, 8 }, { 1015, 10 }, { 4086, 12 }, { 32706, 15 }, { 65420, 16 }, { 65421, 16 }, { 65422, 16 }, { 65423, 16 }, { 65424, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 27, 5 }, { 248, 8 }, { 1016, 10 }, { 4087, 12 }, { 65425, 16 }, { 65426, 16 }, { 65427, 16 }, { 65428, 16 }, { 65429, 16 }, { 65430, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 58, 6 }, { 502, 9 }, { 65431, 16 }, { 65432, 16 }, { 65433, 16 }, { 65434, 16 }, { 65435, 16 }, { 65436, 16 }, { 65437, 16 }, { 65438, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 59, 6 }, { 1017, 10 }, { 65439, 16 }, { 65440, 16 }, { 65441, 16 }, { 65442, 16 }, { 65443, 16 }, { 65444, 16 }, { 65445, 16 }, { 65446, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 121, 7 }, { 2039, 11 }, { 65447, 16 }, { 65448, 16 }, { 65449, 16 }, { 65450, 16 }, { 65451, 16 }, { 65452, 16 }, { 65453, 16 }, { 65454, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 122, 7 }, { 2040, 11 }, { 65455, 16 }, { 65456, 16 }, { 65457, 16 }, { 65458, 16 }, { 65459, 16 }, { 65460, 16 }, { 65461, 16 }, { 65462, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 249, 8 }, { 65463, 16 }, { 65464, 16 }, { 65465, 16 }, { 65466, 16 }, { 65467, 16 }, { 65468, 16 }, { 65469, 16 }, { 65470, 16 }, { 65471, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 503, 9 }, { 65472, 16 }, { 65473, 16 }, { 65474, 16 }, { 65475, 16 }, { 65476, 16 }, { 65477, 16 }, { 65478, 16 }, { 65479, 16 }, { 65480, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 504, 9 }, { 65481, 16 }, { 65482, 16 }, { 65483, 16 }, { 65484, 16 }, { 65485, 16 }, { 65486, 16 }, { 65487, 16 }, { 65488, 16 }, { 65489, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 505, 9 }, { 65490, 16 }, { 65491, 16 }, { 65492, 16 }, { 65493, 16 }, { 65494, 16 }, { 65495, 16 }, { 65496, 16 }, { 65497, 16 }, { 65498, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 506, 9 }, { 65499, 16 }, { 65500, 16 }, { 65501, 16 }, { 65502, 16 }, { 65503, 16 }, { 65504, 16 }, { 65505, 16 }, { 65506, 16 }, { 65507, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 2041, 11 }, { 65508, 16 }, { 65509, 16 }, { 65510, 16 }, { 65511, 16 }, { 65512, 16 }, { 65513, 16 }, { 65514, 16 }, { 65515, 16 }, { 65516, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 16352, 14 }, { 65517, 16 }, { 65518, 16 }, { 65519, 16 }, { 65520, 16 }, { 65521, 16 }, { 65522, 16 }, { 65523, 16 }, { 65524, 16 }, { 65525, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
					{ 1018, 10 }, { 32707, 15 }, { 65526, 16 }, { 65527, 16 }, { 65528, 16 }, { 65529, 16 }, { 65530, 16 }, { 65531, 16 }, { 65532, 16 }, { 65533, 16 }, { 65534, 16 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }
				};
				const int YQT[] = { 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99 };
				const int UVQT[] = { 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99 };
				const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };

				if (!data || !filename || !width || !height || comp > 4 || comp < 1 || comp == 2) {
					return false;
				}

				FILE *fp = fopen(filename, "wb");
				if (!fp) {
					return false;
				}

				quality = quality ? quality : 90;
				quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
				quality = quality < 50 ? 5000 / quality : 200 - quality * 2;

				unsigned char YTable[64], UVTable[64];
				for (int i = 0; i < 64; ++i) {
					int yti = (YQT[i] * quality + 50) / 100;
					YTable[s_jo_ZigZag[i]] = yti < 1 ? 1 : yti > 255 ? 255 : yti;
					int uvti = (UVQT[i] * quality + 50) / 100;
					UVTable[s_jo_ZigZag[i]] = uvti < 1 ? 1 : uvti > 255 ? 255 : uvti;
				}

				float fdtbl_Y[64], fdtbl_UV[64];
				for (int row = 0, k = 0; row < 8; ++row) {
					for (int col = 0; col < 8; ++col, ++k) {
						fdtbl_Y[k] = 1 / (YTable[s_jo_ZigZag[k]] * aasf[row] * aasf[col]);
						fdtbl_UV[k] = 1 / (UVTable[s_jo_ZigZag[k]] * aasf[row] * aasf[col]);
					}
				}

				// Write Headers
				const unsigned char head0[] = { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 'J', 'F', 'I', 'F', 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0xFF, 0xDB, 0, 0x84, 0 };
				fwrite(head0, sizeof(head0), 1, fp);
				fwrite(YTable, sizeof(YTable), 1, fp);
				putc(1, fp);
				fwrite(UVTable, sizeof(UVTable), 1, fp);
				const unsigned char head1[] = { 0xFF, 0xC0, 0, 0x11, 8, (unsigned char)(height >> 8), (unsigned char)(height & 0xFF), (unsigned char)(width >> 8), (unsigned char)(width & 0xFF), 3, 1, 0x11, 0, 2, 0x11, 1, 3, 0x11, 1, 0xFF, 0xC4, 0x01, 0xA2, 0 };
				fwrite(head1, sizeof(head1), 1, fp);
				fwrite(std_dc_luminance_nrcodes + 1, sizeof(std_dc_luminance_nrcodes)-1, 1, fp);
				fwrite(std_dc_luminance_values, sizeof(std_dc_luminance_values), 1, fp);
				putc(0x10, fp); // HTYACinfo
				fwrite(std_ac_luminance_nrcodes + 1, sizeof(std_ac_luminance_nrcodes)-1, 1, fp);
				fwrite(std_ac_luminance_values, sizeof(std_ac_luminance_values), 1, fp);
				putc(1, fp); // HTUDCinfo
				fwrite(std_dc_chrominance_nrcodes + 1, sizeof(std_dc_chrominance_nrcodes)-1, 1, fp);
				fwrite(std_dc_chrominance_values, sizeof(std_dc_chrominance_values), 1, fp);
				putc(0x11, fp); // HTUACinfo
				fwrite(std_ac_chrominance_nrcodes + 1, sizeof(std_ac_chrominance_nrcodes)-1, 1, fp);
				fwrite(std_ac_chrominance_values, sizeof(std_ac_chrominance_values), 1, fp);
				const unsigned char head2[] = { 0xFF, 0xDA, 0, 0xC, 3, 1, 0, 2, 0x11, 3, 0x11, 0, 0x3F, 0 };
				fwrite(head2, sizeof(head2), 1, fp);

				// Encode 8x8 macroblocks
				const unsigned char *imageData = (const unsigned char *)data;
				int DCY = 0, DCU = 0, DCV = 0;
				int bitBuf = 0, bitCnt = 0;
				int ofsG = comp > 1 ? 1 : 0, ofsB = comp > 1 ? 2 : 0;
				for (int y = 0; y < height; y += 8) {
					for (int x = 0; x < width; x += 8) {
						float YDU[64], UDU[64], VDU[64];
						for (int row = y, pos = 0; row < y + 8; ++row) {
							for (int col = x; col < x + 8; ++col, ++pos) {
								int p = row*width*comp + col*comp;
								if (row >= height) {
									p -= width*comp*(row + 1 - height);
								}
								if (col >= width) {
									p -= comp*(col + 1 - width);
								}

								float r = imageData[p + 0], g = imageData[p + ofsG], b = imageData[p + ofsB];
								YDU[pos] = +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
								UDU[pos] = -0.16874f*r - 0.33126f*g + 0.50000f*b;
								VDU[pos] = +0.50000f*r - 0.41869f*g - 0.08131f*b;
							}
						}

						DCY = jo_processDU(fp, bitBuf, bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
						DCU = jo_processDU(fp, bitBuf, bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
						DCV = jo_processDU(fp, bitBuf, bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
					}
				}

				// Do the bit alignment of the EOI marker
				const unsigned short fillBits[] = { 0x7F, 7 };
				jo_writeBits(fp, bitBuf, bitCnt, fillBits);

				// EOI
				putc(0xFF, fp);
				putc(0xD9, fp);

				fclose(fp);
				return true;
			}
		} // namespace jo
	}
	// \endcond

	namespace fmt
	{
		namespace consts
		{
			static const char			kFormatSpecifierEscapeChar = '%';
			static const std::string	kZeroPaddingStr = std::string("0");
		}

		bool is_digit(char c)
		{
			return c >= '0' && c <= '9';
		}

		bool wild_card_match(const char* str, const char* pattern)
		{
			while (*pattern)
			{
				switch (*pattern)
				{
				case '?':
					if (!*str) return false;
					++str;
					++pattern;
					break;
				case '*':
					if (wild_card_match(str, pattern + 1)) return true;
					if (*str && wild_card_match(str + 1, pattern)) return true;
					return false;
					break;
				default:
					if (*str++ != *pattern++) return false;
					break;
				}
			}
			return !*str && !*pattern;
		}

		std::string ltrim(std::string str)
		{
			str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int c) {return !std::isspace(c); }));

			return str;
		}

		std::string rtrim(std::string s)
		{
			s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
				return !std::isspace(ch);
			}).base(), s.end());

			return s;
		}

		std::string trim(std::string str)
		{
			return ltrim(rtrim(str));
		}

		std::string lstrip(std::string str, std::string what)
		{
			auto pos = str.find(what);
			if (0 == pos)
			{
				str.erase(pos, what.length());
			}
			return str;
		}

		std::string rstrip(std::string str, std::string what)
		{
			auto pos = str.rfind(what);
			if (str.length() - what.length() == pos)
			{
				str.erase(pos, what.length());
			}
			return str;
		}

		std::string lskip(std::string str, std::string delim)
		{
			auto pos = str.find(delim);
			if (pos == std::string::npos)
			{
				str = std::string();
			}
			else
			{
				str = str.substr(pos + 1);
			}
			return str;
		}

		std::string rskip(std::string str, std::string delim)
		{
			auto pos = str.rfind(delim);
			if (pos == 0)
			{
				str = std::string();
			}
			else if (std::string::npos != pos)
			{
				str = str.substr(0, pos);
			}
			return str;
		}

		std::string rskip_all(std::string str, std::string delim)
		{
			auto pos = str.find(delim);
			if (pos == std::string::npos)
			{
				return str;
			}
			else
			{
				return str.substr(0, pos);
			}
		}

		std::vector<std::string> split(const std::string s, char delim)
		{
			std::vector<std::string> elems;
			std::istringstream ss(s);
			std::string item;
			while (std::getline(ss, item, delim))
			{
				if (!item.empty()) elems.push_back(item);
			}
			return elems;
		}

		std::vector<std::string> split(const std::string s, std::string delim)
		{
			std::vector<std::string> elems;
			std::string ss(s);
			std::string item;
			size_t pos = 0;
			while ((pos = ss.find(delim)) != std::string::npos) {
				item = ss.substr(0, pos);
				if (!item.empty()) elems.push_back(item);
				ss.erase(0, pos + delim.length());
			}
			if (!ss.empty()) elems.push_back(ss);
			return elems;
		}

		std::vector<std::string> split_multi_delims(const std::string s, std::string delims)
		{
			std::vector<std::string> elems;
			std::string ss(s);
			std::string item;
			size_t pos = 0;
			while ((pos = ss.find_first_of(delims)) != std::string::npos) {
				item = ss.substr(0, pos);
				if (!item.empty()) elems.push_back(item);
				ss.erase(0, pos + 1);
			}
			if (!ss.empty()) elems.push_back(ss);
			return elems;
		}

		std::vector<std::string> split_whitespace(const std::string s)
		{
			auto list = split_multi_delims(s, " \t\n");
			std::vector<std::string> ret;
			for (auto elem : list)
			{
				auto rest = fmt::trim(elem);
				if (!rest.empty()) ret.push_back(rest);
			}
			return ret;
		}

		std::pair<std::string, std::string> split_first_occurance(const std::string s, char delim)
		{
			auto pos = s.find_first_of(delim);
			std::string first = s.substr(0, pos);
			std::string second = (pos != std::string::npos ? s.substr(pos + 1) : std::string());
			return std::make_pair(first, second);
		}

		std::vector<std::string>& erase_empty(std::vector<std::string> &vec)
		{
			for (auto it = vec.begin(); it != vec.end();)
			{
				if (it->empty())
				{
					it = vec.erase(it);
				}
				else
				{
					++it;
				}
			}
			return vec;
		}

		std::string join(std::vector<std::string> elems, char delim)
		{
			std::string str;
			elems = erase_empty(elems);
			if (elems.empty()) return str;
			str = elems[0];
			for (std::size_t i = 1; i < elems.size(); ++i)
			{
				if (elems[i].empty()) continue;
				str += delim + elems[i];
			}
			return str;
		}

		bool starts_with(const std::string& str, const std::string& start)
		{
			return (str.length() >= start.length()) && (str.compare(0, start.length(), start) == 0);
		}

		bool ends_with(const std::string& str, const std::string& end)
		{
			return (str.length() >= end.length()) && (str.compare(str.length() - end.length(), end.length(), end) == 0);
		}

		std::string& replace_all(std::string& str, char replaceWhat, char replaceWith)
		{
			std::replace(str.begin(), str.end(), replaceWhat, replaceWith);
			return str;
		}

		std::string& replace_all(std::string& str, const std::string& replaceWhat, const std::string& replaceWith)
		{
			if (replaceWhat == replaceWith) return str;
			std::size_t foundAt = std::string::npos;
			while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos)
			{
				str.replace(foundAt, replaceWhat.length(), replaceWith);
			}
			return str;
		}

		void replace_first_with_escape(std::string &str, const std::string &replaceWhat, const std::string &replaceWith)
		{
			std::size_t foundAt = std::string::npos;
			while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos)
			{
				if (foundAt > 0 && str[foundAt - 1] == consts::kFormatSpecifierEscapeChar)
				{
					str.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
					++foundAt;
				}
				else
				{
					str.replace(foundAt, replaceWhat.length(), replaceWith);
					return;
				}
			}
		}

		void replace_all_with_escape(std::string &str, const std::string &replaceWhat, const std::string &replaceWith)
		{
			std::size_t foundAt = std::string::npos;
			while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos)
			{
				if (foundAt > 0 && str[foundAt - 1] == consts::kFormatSpecifierEscapeChar)
				{
					str.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
					++foundAt;
				}
				else
				{
					str.replace(foundAt, replaceWhat.length(), replaceWith);
					foundAt += replaceWith.length();
				}
			}
		}

		void replace_sequential_with_escape(std::string &str, const std::string &replaceWhat, const std::vector<std::string> &replaceWith)
		{
			std::size_t foundAt = std::string::npos;
			std::size_t candidatePos = 0;
			while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos && replaceWith.size() > candidatePos)
			{
				if (foundAt > 0 && str[foundAt - 1] == consts::kFormatSpecifierEscapeChar)
				{
					str.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
					++foundAt;
				}
				else
				{
					str.replace(foundAt, replaceWhat.length(), replaceWith[candidatePos]);
					foundAt += replaceWith[candidatePos].length();
					++candidatePos;
				}
			}
		}

		bool str_equals(const char* s1, const char* s2)
		{
			if (s1 == nullptr && s2 == nullptr) return true;
			if (s1 == nullptr || s2 == nullptr) return false;
			return std::string(s1) == std::string(s2);	// this is safe, not with strcmp
		}

		//std::string& left_zero_padding(std::string &str, int width)
		//{
		//	int toPad = width - static_cast<int>(str.length());
		//	while (toPad > 0)
		//	{
		//		str = consts::kZeroPaddingStr + str;
		//		--toPad;
		//	}
		//	return str;
		//}

		std::string to_lower_ascii(std::string mixed)
		{
			std::transform(mixed.begin(), mixed.end(), mixed.begin(), ::tolower);
			return mixed;
		}

		std::string to_upper_ascii(std::string mixed)
		{
			std::transform(mixed.begin(), mixed.end(), mixed.begin(), ::toupper);
			return mixed;
		}

		std::u16string utf8_to_utf16(std::string u8str)
		{
			try
			{
				std::u16string ret;
				thirdparty::utf8::utf8to16(u8str.begin(), u8str.end(), std::back_inserter(ret));
				return ret;
			}
			catch (...)
			{
				throw RuntimeException("Invalid UTF-8 string.");
			}
		}

		std::string utf16_to_utf8(std::u16string u16str)
		{
			try
			{
				std::vector<unsigned char> u8vec;
				thirdparty::utf8::utf16to8(u16str.begin(), u16str.end(), std::back_inserter(u8vec));
				auto ptr = reinterpret_cast<char*>(u8vec.data());
				return std::string(ptr, ptr + u8vec.size());
			}
			catch (...)
			{
				throw RuntimeException("Invalid UTF-16 string.");
			}
		}

		std::u32string utf8_to_utf32(std::string u8str)
		{
			try
			{
				std::u32string ret;
				thirdparty::utf8::utf8to32(u8str.begin(), u8str.end(), std::back_inserter(ret));
				return ret;
			}
			catch (...)
			{
				throw RuntimeException("Invalid UTF-8 string.");
			}
		}

		std::string utf32_to_utf8(std::u32string u32str)
		{
			try
			{
				std::vector<unsigned char> u8vec;
				thirdparty::utf8::utf32to8(u32str.begin(), u32str.end(), std::back_inserter(u8vec));
				auto ptr = reinterpret_cast<char*>(u8vec.data());
				return std::string(ptr, ptr + u8vec.size());
			}
			catch (...)
			{
				throw RuntimeException("Invalid UTF-32 string.");
			}
		}

	} // namespace fmt

	namespace fs
	{
		Path::Path(std::string path, bool isAbsolute)
		{
			if (isAbsolute) abspath_ = path;
			else abspath_ = os::absolute_path(path);
		}

		bool Path::empty() const
		{
			return abspath_.empty();
		}

		bool Path::exist() const
		{
			if (empty()) return false;
			return os::path_exists(abspath_, true);
		}

		bool Path::is_file() const
		{
			return os::is_file(abspath_);
		}

		bool Path::is_dir() const
		{
			return os::is_directory(abspath_);
		}

		std::string Path::abs_path() const
		{
			return abspath_;
		}

		std::string Path::relative_path() const
		{
			std::string cwd = os::current_working_directory() + os::path_delim();
			if (fmt::starts_with(abspath_, cwd))
			{
				return fmt::lstrip(abspath_, cwd);
			}
			return relative_path(cwd);
		}

		std::string Path::relative_path(std::string root) const
		{
			if (!fmt::ends_with(root, os::path_delim())) root += os::path_delim();
			if (fmt::starts_with(abspath_, root))
			{
				return fmt::lstrip(abspath_, root);
			}

			auto rootParts = os::path_split(root);
			auto thisParts = os::path_split(abspath_);
			std::size_t commonParts = 0;
			std::size_t size = (std::min)(rootParts.size(), thisParts.size());
			for (std::size_t i = 0; i < size; ++i)
			{
				if (!os::path_identical(rootParts[i], thisParts[i])) break;
				++commonParts;
			}

			if (commonParts == 0)
			{
				log::detail::zupply_internal_warn("Unable to resolve relative path to: " + root + "! Return absolute path.");
				return abspath_;
			}

			std::vector<std::string> tmp;
			// traverse back from root, add ../ to path
			for (std::size_t pos = rootParts.size(); pos > commonParts; --pos)
			{
				tmp.push_back("..");
			}
			// forward add parts of this path
			for (std::size_t pos = commonParts; pos < thisParts.size(); ++pos)
			{
				tmp.push_back(thisParts[pos]);
			}
			return os::path_join(tmp);
		}

		std::string Path::filename() const
		{
			if (is_file()) return os::path_split_filename(abspath_);
			return std::string();
		}

		Directory::Directory(std::string root, bool recursive)
			:root_(root), recursive_(recursive)
		{
			resolve();
		}

                Directory::Directory(std::string root, const std::string pattern, bool recursive)
			: root_(root), recursive_(recursive)
		{
			resolve();
			filter(pattern);
		}

                Directory::Directory(std::string root, const std::vector<std::string> patterns, bool recursive)
                        : root_(root), recursive_(recursive)
                {
                        resolve();
                        filter(patterns);
                }

                Directory::Directory(std::string root, const std::vector<const char*> patterns, bool recursive)
                        : root_(root), recursive_(recursive)
                {
                        resolve();
                        filter(patterns);
                }

		bool Directory::is_recursive() const
		{
			return recursive_;
		}

		std::string Directory::root() const
		{
			return root_.abs_path();
		}

		std::vector<Path> Directory::to_list() const
		{
			return paths_;
		}

		void Directory::resolve()
		{
			std::deque<std::string> workList;
			if (root_.is_dir())
			{
				workList.push_back(root_.abs_path());
			}

			while (!workList.empty())
			{
				std::vector<std::string> list = os::list_directory(workList.front());
				workList.pop_front();
				for (auto i = list.begin(); i != list.end(); ++i)
				{
					if (os::is_file(*i))
					{
						paths_.push_back(Path(*i));
					}
					else if (os::is_directory(*i))
					{
						paths_.push_back(Path(*i));
						if (recursive_) workList.push_back(*i);	// add subdir work list
					}
					else
					{
						// this should not happen unless file/dir modified during runtime
						// ignore the error
					}
				}
			}
		}

                void Directory::filter(const std::string pattern)
		{
			std::vector<Path> filtered;
			for (auto entry : paths_)
			{
				if (entry.is_file())
				{
					std::string filename = os::path_split_filename(entry.abs_path());
					if (fmt::wild_card_match(filename.c_str(), pattern.c_str()))
					{
						filtered.push_back(entry);
					}
				}
			}
			paths_ = filtered;	// replace all
		}

                void Directory::filter(const std::vector<std::string> patterns)
                {
                    std::vector<Path> filtered;
                    for (auto entry : paths_)
                    {
                            if (entry.is_file())
                            {
                                    std::string filename = os::path_split_filename(entry.abs_path());
                                    for (auto pattern : patterns)
                                    {
                                        if (fmt::wild_card_match(filename.c_str(), pattern.c_str()))
                                        {
                                                filtered.push_back(entry);
                                        }
                                    }
                            }
                    }
                    paths_ = filtered;	// replace all
                }

                void Directory::filter(const std::vector<const char*> patterns)
                {
                    std::vector<Path> filtered;
                    for (auto entry : paths_)
                    {
                            if (entry.is_file())
                            {
                                    std::string filename = os::path_split_filename(entry.abs_path());
                                    for (auto pattern : patterns)
                                    {
                                        if (fmt::wild_card_match(filename.c_str(), pattern))
                                        {
                                                filtered.push_back(entry);
                                        }
                                    }
                            }
                    }
                    paths_ = filtered;	// replace all
                }

		void Directory::reset()
		{
			resolve();
		}

		FileEditor::FileEditor(std::string filename, bool truncateOrNot, int retryTimes, int retryInterval)
		{
			// use absolute path
			filename_ = os::absolute_path(filename);
			// try open
			this->try_open(retryTimes, retryInterval, truncateOrNot);
		};

		//FileEditor::FileEditor(FileEditor&& other) : filename_(std::move(other.filename_)),
		//	stream_(std::move(other.stream_)),
		//	readPos_(std::move(other.readPos_)),
		//	writePos_(std::move(other.writePos_))
		//{
		//	other.filename_ = std::string();
		//};

		bool FileEditor::open(bool truncateOrNot)
		{
			if (this->is_open()) return true;
			std::ios::openmode mode = std::ios::in | std::ios::out | std::ios::binary;
			if (truncateOrNot)
			{
				mode |= std::ios::trunc;
			}
			else
			{
				mode |= std::ios::app;
			}
			os::fstream_open(stream_, filename_, mode);
			if (this->is_open()) return true;
			return false;
		}

		bool FileEditor::open(const char* filename, bool truncateOrNot, int retryTimes, int retryInterval)
		{
			this->close();
			// use absolute path
			filename_ = os::absolute_path(filename);
			// try open
			return this->try_open(retryTimes, retryInterval, truncateOrNot);
		}

		bool FileEditor::open(std::string filename, bool truncateOrNot, int retryTimes, int retryInterval)
		{
			this->close();
			// use absolute path
			filename_ = os::absolute_path(filename);
			// try open
			return this->try_open(retryTimes, retryInterval, truncateOrNot);
		}

		bool FileEditor::reopen(bool truncateOrNot)
		{
			this->close();
			return this->open(truncateOrNot);
		}

		void FileEditor::close()
		{
			stream_.close();
			stream_.clear();
			// unregister this file
			//detail::FileEditorRegistry::instance().erase(filename_);
		}

		bool FileEditor::try_open(int retryTime, int retryInterval, bool truncateOrNot)
		{
			while (retryTime > 0 && (!this->open(truncateOrNot)))
			{
				time::sleep(retryInterval);
				--retryTime;
			}
			return this->is_open();
		}

		FileReader::FileReader(std::string filename, int retryTimes, int retryInterval)
		{
			// use absolute path
			filename_ = os::absolute_path(filename);
			// try open
			this->try_open(retryTimes, retryInterval);
		}

		//FileReader::FileReader(FileReader&& other) : filename_(std::move(other.filename_)), istream_(std::move(other.istream_))
		//{
		//	other.filename_ = std::string();
		//};

		bool FileReader::open()
		{
			if (this->is_open()) return true;
			this->check_valid();
			os::ifstream_open(istream_, filename_, std::ios::in | std::ios::binary);
			if (this->is_open()) return true;
			return false;
		}

		bool FileReader::try_open(int retryTime, int retryInterval)
		{
			while (retryTime > 0 && (!this->open()))
			{
				time::sleep(retryInterval);
				--retryTime;
			}
			return this->is_open();
		}

		std::size_t FileReader::file_size()
		{
			if (is_open())
			{
				auto curPos = istream_.tellg();
				istream_.seekg(0, istream_.end);
				std::size_t size = static_cast<std::size_t>(istream_.tellg());
				istream_.seekg(curPos);
				return size;
			}
			return 0;
		}

		std::size_t FileReader::count_lines()
		{
			// store current read location
			std::streampos readPtrBackup = istream_.tellg();
			istream_.seekg(std::ios_base::beg);

			const int bufSize = 1024 * 1024;	// using 1MB buffer
			std::vector<char> buf(bufSize);

			std::size_t ct = 0;
			std::streamsize nbuf = 0;
			char last = 0;
			do
			{
				istream_.read(&buf.front(), bufSize);
				nbuf = istream_.gcount();
				for (auto i = 0; i < nbuf; i++)
				{
					last = buf[i];
					if (last == '\n')
					{
						++ct;
					}
				}
			} while (nbuf > 0);

			if (last != '\n') ++ct;

			// restore read position
			istream_.clear();
			istream_.seekg(readPtrBackup);

			return ct;
		}

		std::string FileReader::next_line(bool trimWhitespaces)
		{
			std::string line;

			if (!istream_.good() || !istream_.is_open())
			{
				return line;
			}

			if (!istream_.eof())
			{
				std::getline(istream_, line);
				if (trimWhitespaces) return fmt::trim(line);
				if (line.back() == '\r')
				{
					// LFCR \r\n problem
					line.pop_back();
				}
			}
			return line;
		}

		int FileReader::goto_line(int n)
		{
			if (!istream_.good() || !istream_.is_open())
				return -1;

			istream_.seekg(std::ios::beg);

			if (n < 0)
			{
				throw ArgException("Jumping to a negtive position!");
			}

			if (n == 0)
			{
				return 0;
			}

			int i = 0;
			for (i = 0; i < n - 1; ++i)
			{

				istream_.ignore((std::numeric_limits<std::streamsize>::max)(), '\n');

				if (istream_.eof())
				{
					log::detail::zupply_internal_warn("Reached end of file, line: " + std::to_string(i + 1));
					break;
				}
			}

			return i + 1;
		}

		std::size_t get_file_size(std::string filename)
		{
			std::ifstream fs;
			os::ifstream_open(fs, filename, std::ios::in | std::ios::ate);
			std::size_t size = static_cast<std::size_t>(fs.tellg());
			return size;
		}

	} //namespace fs



	namespace time
	{
		namespace consts
		{
			static const unsigned		kMaxDateTimeLength = 2048;
			static const char			*kDateFractionSpecifier = "%frac";
			static const unsigned	    kDateFractionWidth = 3;
			static const char			*kTimerPrecisionSecSpecifier = "%sec";
			static const char			*kTimerPrecisionMsSpecifier = "%ms";
			static const char			*kTimerPrecisionUsSpecifier = "%us";
			static const char			*kTimerPrecisionNsSpecifier = "%ns";
			static const char			*kDateTimeSpecifier = "%datetime";
		}

		DateTime::DateTime()
		{
			auto now = std::chrono::system_clock::now();
			timeStamp_ = std::chrono::system_clock::to_time_t(now);
			calendar_ = os::localtime(timeStamp_);
			fraction_ = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()
				% math::Pow<10, consts::kDateFractionWidth>::result;
			fractionStr_ = fmt::int_to_zero_pad_str(fraction_, consts::kDateFractionWidth);
		}

		void DateTime::to_local_time()
		{
			calendar_ = os::localtime(timeStamp_);
		}

		void DateTime::to_utc_time()
		{
			calendar_ = os::gmtime(timeStamp_);
		}

		std::string DateTime::to_string(const char *format)
		{
			std::string fmt(format);
			fmt::replace_all_with_escape(fmt, consts::kDateFractionSpecifier, fractionStr_);
			std::vector<char> mbuf(fmt.length() + 100);
			std::size_t size = strftime(mbuf.data(), mbuf.size(), fmt.c_str(), &calendar_);
			while (size == 0)
			{
				if (mbuf.size() > consts::kMaxDateTimeLength)
				{
					return std::string("String size exceed limit!");
				}
				mbuf.resize(mbuf.size() * 2);
				size = strftime(mbuf.data(), mbuf.size(), fmt.c_str(), &calendar_);
			}
			return std::string(mbuf.begin(), mbuf.begin() + size);
		}

		DateTime DateTime::local_time()
		{
			DateTime date;
			return date;
		}

		DateTime DateTime::utc_time()
		{
			DateTime date;
			date.to_utc_time();
			return date;
		}

		Timer::Timer()
		{
			this->reset();
		};

		void Timer::reset()
		{
			timeStamp_ = std::chrono::steady_clock::now();
			elapsed_ = 0;
			paused_ = false;
		}

		void Timer::pause()
		{
			if (paused_) return;
			elapsed_ = elapsed_ns();

			paused_ = true;
		}

		void Timer::resume()
		{
			if (!paused_) return;
			timeStamp_ = std::chrono::steady_clock::now();
			paused_ = false;
		}

		std::size_t	Timer::elapsed_ns()
		{
			if (paused_) return elapsed_;
			return static_cast<std::size_t>(std::chrono::duration_cast<std::chrono::nanoseconds>
				(std::chrono::steady_clock::now() - timeStamp_).count()) + elapsed_;
		}

		std::string Timer::elapsed_ns_str()
		{
			return std::to_string(elapsed_ns());
		}

		std::size_t Timer::elapsed_us()
		{
			return elapsed_ns() / 1000;
		}

		std::string Timer::elapsed_us_str()
		{
			return std::to_string(elapsed_us());
		}

		std::size_t Timer::elapsed_ms()
		{
			return elapsed_ns() / 1000000;
		}

		std::string Timer::elapsed_ms_str()
		{
			return std::to_string(elapsed_ms());
		}

		std::size_t Timer::elapsed_sec()
		{
			return elapsed_ns() / 1000000000;
		}

		std::string Timer::elapsed_sec_str()
		{
			return std::to_string(elapsed_sec());
		}

		double Timer::elapsed_sec_double()
		{
			return static_cast<double>(elapsed_ns()) / 1000000000;
		}

		std::string Timer::to_string(const char *format)
		{
			std::string str(format);
			fmt::replace_all_with_escape(str, consts::kTimerPrecisionSecSpecifier, elapsed_sec_str());
			fmt::replace_all_with_escape(str, consts::kTimerPrecisionMsSpecifier, elapsed_ms_str());
			fmt::replace_all_with_escape(str, consts::kTimerPrecisionUsSpecifier, elapsed_us_str());
			fmt::replace_all_with_escape(str, consts::kTimerPrecisionNsSpecifier, elapsed_ns_str());
			return str;
		}


	} // namespace time



	namespace os
	{
		namespace consts
		{
			static const std::string kEndLineCRLF = std::string("\r\n");
			static const std::string kEndLineLF = std::string("\n");
			static const std::string kNativePathDelimWindows = "\\";
			static const std::string kNativePathDelimPosix = "/";
		}

		int system(const char *const command, const char *const moduleName)
		{
			misc::unused(moduleName);
#if ZUPPLY_OS_WINDOWS
			PROCESS_INFORMATION pi;
			STARTUPINFO si;
			std::memset(&pi, 0, sizeof(PROCESS_INFORMATION));
			std::memset(&si, 0, sizeof(STARTUPINFO));
			GetStartupInfoA(&si);
			si.cb = sizeof(si);
			si.wShowWindow = SW_HIDE;
			si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW;
			const BOOL res = CreateProcessA((LPCTSTR)moduleName, (LPTSTR)command, 0, 0, FALSE, 0, 0, 0, &si, &pi);
			if (res) {
				WaitForSingleObject(pi.hProcess, INFINITE);
				CloseHandle(pi.hThread);
				CloseHandle(pi.hProcess);
				return 0;
			}
			else return std::system(command);
#elif ZUPPLY_OS_UNIX
			const unsigned int l = std::strlen(command);
			if (l) {
				char *const ncommand = new char[l + 16];
				std::strncpy(ncommand, command, l);
				std::strcpy(ncommand + l, " 2> /dev/null"); // Make command silent.
				const int out_val = std::system(ncommand);
				delete[] ncommand;
				return out_val;
			}
			else return -1;
#else
			misc::unused(command);
			return -1;
#endif
		}

		std::size_t thread_id()
		{
#if ZUPPLY_OS_WINDOWS
			// It exists because the std::this_thread::get_id() is much slower(espcially under VS 2013)
			return  static_cast<size_t>(::GetCurrentThreadId());
#elif __linux__
			return  static_cast<size_t>(syscall(SYS_gettid));
#else //Default to standard C++11 (OSX and other Unix)
			static std::mutex threadIdMutex;
			static std::map<std::thread::id, std::size_t> threadIdHashmap;
			std::lock_guard<std::mutex> lock(threadIdMutex);
			auto id = std::this_thread::get_id();
			if (threadIdHashmap.count(id) < 1)
			{
				threadIdHashmap[id] = threadIdHashmap.size() + 1;
			}
			return threadIdHashmap[id];
			//return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id()));
#endif

		}

		int is_atty()
		{
#if ZUPPLY_OS_WINDOWS
			return _isatty(_fileno(stdout));
#elif ZUPPLY_OS_UNIX
			return isatty(fileno(stdout));
#endif
			return 0;
		}

		Size console_size()
		{
			Size ret(-1, -1);
#if ZUPPLY_OS_WINDOWS
			CONSOLE_SCREEN_BUFFER_INFO csbi;
			GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
			ret.width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
			ret.height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
#elif ZUPPLY_OS_UNIX
			struct winsize w;
			ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
			ret.width = w.ws_col;
			ret.height = w.ws_row;
#endif
			return ret;
		}

		std::tm localtime(std::time_t t)
		{
			std::tm temp;
#if ZUPPLY_OS_WINDOWS
			localtime_s(&temp, &t);
			return temp;
#elif ZUPPLY_OS_UNIX
			// POSIX SUSv2 thread safe localtime_r
			return *localtime_r(&t, &temp);
#else
			return temp; // return default tm struct, no idea what it is
#endif
		}

		std::tm gmtime(std::time_t t)
		{
			std::tm temp;
#if ZUPPLY_OS_WINDOWS
			gmtime_s(&temp, &t);
			return temp;
#elif ZUPPLY_OS_UNIX
			// POSIX SUSv2 thread safe gmtime_r
			return *gmtime_r(&t, &temp);
#else
			return temp; // return default tm struct, no idea what it is
#endif
		}

		std::wstring utf8_to_wstring(std::string &u8str)
		{
#if ZUPPLY_OS_WINDOWS
			// windows use 16 bit wstring
			std::u16string u16str = fmt::utf8_to_utf16(u8str);
			return std::wstring(u16str.begin(), u16str.end());
#else
			// otherwise use 32 bit wstring
			std::u32string u32str = fmt::utf8_to_utf32(u8str);
			return std::wstring(u32str.begin(), u32str.end());
#endif
		}

		std::string wstring_to_utf8(std::wstring &wstr)
		{
#if ZUPPLY_OS_WINDOWS
			// windows use 16 bit wstring
			std::u16string u16str(wstr.begin(), wstr.end());
			return fmt::utf16_to_utf8(u16str);
#else
			// otherwise use 32 bit wstring
			std::u32string u32str(wstr.begin(), wstr.end());
			return fmt::utf32_to_utf8(u32str);
#endif
		}

		std::vector<std::string> path_split(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			std::replace(path.begin(), path.end(), '\\', '/');
			return fmt::split(path, '/');
#else
			return fmt::split(path, '/');
#endif
		}

		std::string path_join(std::vector<std::string> elems)
		{
#if ZUPPLY_OS_WINDOWS
			return fmt::join(elems, '\\');
#else
			return fmt::join(elems, '/');
#endif
		}

		std::string path_split_filename(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			std::string::size_type pos = fmt::trim(path).find_last_of("/\\");
#else
			std::string::size_type pos = fmt::trim(path).find_last_of("/");
#endif
			if (pos == std::string::npos) return path;
			if (pos != path.length())
			{
				return path.substr(pos + 1);
			}
			return std::string();
		}

		std::string path_split_directory(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			std::string::size_type pos = fmt::trim(path).find_last_of("/\\");
#else
			std::string::size_type pos = fmt::trim(path).find_last_of("/");
#endif
			if (pos != std::string::npos)
			{
				return path.substr(0, pos);
			}
			return std::string();
		}

		std::string path_split_extension(std::string path)
		{
			std::string filename = path_split_filename(path);
			auto list = fmt::split(filename, '.');
			if (list.size() > 1)
			{
				return list.back();
			}
			return std::string();
		}

		std::string path_split_basename(std::string path)
		{
			std::string filename = path_split_filename(path);
			auto list = fmt::split(filename, '.');
			if (list.size() == 1)
			{
				return list[0];
			}
			else if (list.size() > 1)
			{
				return fmt::join({ list.begin(), list.end() - 1 }, '.');
			}
			return std::string();
		}

		std::string path_append_basename(std::string origPath, std::string whatToAppend)
		{
			std::string newPath = path_join({ path_split_directory(origPath), path_split_basename(origPath) })
				+ whatToAppend;
			std::string ext = path_split_extension(origPath);
			if (!ext.empty())
			{
				newPath += "." + ext;
			}
			return newPath;
		}


		void fstream_open(std::fstream &stream, std::string filename, std::ios::openmode openmode)
		{
			// make sure directory exists for the target file
			create_directory_recursive(path_split_directory(filename));
#if ZUPPLY_OS_WINDOWS
      char* buffer = new char[filename.size() + 1];
      wcstombs(buffer,utf8_to_wstring(filename).c_str(),filename.size());
			buffer[filename.size()] = '\0';
			stream.open(buffer, openmode);
			delete [] buffer;
#else
			stream.open(filename, openmode);
#endif
		}

		void ifstream_open(std::ifstream &stream, std::string filename, std::ios::openmode openmode)
		{
#if ZUPPLY_OS_WINDOWS
      char* buffer = new char[filename.size() + 1];
      wcstombs(buffer,utf8_to_wstring(filename).c_str(),filename.size());
			buffer[filename.size()] = '\0';
			stream.open(buffer, openmode);
			delete [] buffer;
#else
			stream.open(filename, openmode);
#endif
		}

		bool rename(std::string oldName, std::string newName)
		{
#if ZUPPLY_OS_WINDOWS
			return (!_wrename(utf8_to_wstring(oldName).c_str(), utf8_to_wstring(newName).c_str()));
#else
			return (!::rename(oldName.c_str(), newName.c_str()));
#endif
		}

		bool copyfile(std::string src, std::string dst, bool replaceDst)
		{
			if (!replaceDst)
			{
				if (path_exists(dst, true)) return false;
			}
			remove_all(dst);
			std::ifstream  srcf;
			std::fstream  dstf;
			ifstream_open(srcf, src, std::ios::binary);
			fstream_open(dstf, dst, std::ios::binary | std::ios::trunc | std::ios::out);
			dstf << srcf.rdbuf();
			return true;
		}

		bool movefile(std::string src, std::string dst, bool replaceDst)
		{
			if (!replaceDst)
			{
				if (path_exists(dst, true)) return false;
			}
			return os::rename(src, dst);
		}

		bool remove_all(std::string path)
		{
			if (!os::path_exists(path, true)) return true;
			if (os::is_directory(path))
			{
				return remove_dir(path);
			}
			return remove_file(path);
		}

#if ZUPPLY_OS_UNIX
		// for nftw tree walk directory operations, so for unix only
		int nftw_remove(const char *path, const struct stat *sb, int flag, struct FTW *ftwbuf)
		{
			misc::unused(sb);
			misc::unused(flag);
			misc::unused(ftwbuf);
			return ::remove(path);
		}
#endif

		bool remove_dir(std::string path, bool recursive)
		{
			// as long as dir does not exist, return true
			if (!is_directory(path)) return true;
#if ZUPPLY_OS_WINDOWS
			std::wstring root = utf8_to_wstring(path);
			bool            bSubdirectory = false;       // Flag, indicating whether subdirectories have been found
			HANDLE          hFile;                       // Handle to directory
			std::wstring     strFilePath;                 // Filepath
			std::wstring     strPattern;                  // Pattern
			WIN32_FIND_DATAW FileInformation;             // File information


			strPattern = root + L"\\*.*";
			hFile = ::FindFirstFileW(strPattern.c_str(), &FileInformation);
			if (hFile != INVALID_HANDLE_VALUE)
			{
				do
				{
					if (FileInformation.cFileName[0] != '.')
					{
						strFilePath.erase();
						strFilePath = root + L"\\" + FileInformation.cFileName;

						if (FileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
						{
							if (recursive)
							{
								// Delete subdirectory
								bool iRC = remove_dir(wstring_to_utf8(strFilePath), recursive);
								if (!iRC) return false;
							}
							else
								bSubdirectory = true;
						}
						else
						{
							// Set file attributes
							if (::SetFileAttributesW(strFilePath.c_str(),
								FILE_ATTRIBUTE_NORMAL) == FALSE)
								return false;

							// Delete file
							if (::DeleteFileW(strFilePath.c_str()) == FALSE)
								return false;
						}
					}
				} while (::FindNextFileW(hFile, &FileInformation) == TRUE);

				// Close handle
				::FindClose(hFile);

				DWORD dwError = ::GetLastError();
				if (dwError != ERROR_NO_MORE_FILES)
					return false;
				else
				{
					if (!bSubdirectory)
					{
						// Set directory attributes
						if (::SetFileAttributesW(root.c_str(),
							FILE_ATTRIBUTE_NORMAL) == FALSE)
							return false;

						// Delete directory
						if (::RemoveDirectoryW(root.c_str()) == FALSE)
							return false;
					}
				}
			}

			return true;
#else
			if (recursive) ::nftw(path.c_str(), nftw_remove, 20, FTW_DEPTH);
			else ::remove(path.c_str());
			return (is_directory(path) == false);
#endif
		}

		bool remove_file(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			_wremove(utf8_to_wstring(path).c_str());
#else
			unlink(path.c_str());
#endif
			return (is_file(path) == false);
		}

		std::string last_error()
		{
#if ZUPPLY_OS_WINDOWS
			DWORD error = GetLastError();
			if (error)
			{
				LPVOID lpMsgBuf;
				DWORD bufLen = FormatMessage(
					FORMAT_MESSAGE_ALLOCATE_BUFFER |
					FORMAT_MESSAGE_FROM_SYSTEM |
					FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL,
					error,
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					(LPTSTR)&lpMsgBuf,
					0, NULL);
				if (bufLen)
				{
					LPCSTR lpMsgStr = (LPCSTR)lpMsgBuf;
					std::string result(lpMsgStr, lpMsgStr + bufLen);

					LocalFree(lpMsgBuf);

					return result;
				}
			}
#else
			char* errStr = strerror(errno);
			if (errStr) return std::string(errStr);
#endif
			return std::string();
		}

		std::string endl()
		{
#if ZUPPLY_OS_WINDOWS
			return consts::kEndLineCRLF;
#else // *nix, OSX, and almost everything else(OS 9 or ealier use CR only, but they are antiques now)
			return consts::kEndLineLF;
#endif
		}

		std::string path_delim()
		{
#if ZUPPLY_OS_WINDOWS
			return consts::kNativePathDelimWindows;
#else // posix
			return consts::kNativePathDelimPosix;
#endif
		}

		std::string current_working_directory()
		{
#if ZUPPLY_OS_WINDOWS
			wchar_t *buffer = nullptr;
			if ((buffer = _wgetcwd(nullptr, 0)) == nullptr)
			{
				// failed
				log::detail::zupply_internal_warn("Unable to get current working directory!");
				return std::string(".");
			}
			else
			{
				std::wstring ret(buffer);
				free(buffer);
				return wstring_to_utf8(ret);
			}
#elif _GNU_SOURCE
			char *buffer = realpath(".", nullptr);
			if (buffer == nullptr)
			{
				// failed
				log::detail::zupply_internal_warn("Unable to get current working directory!");
				return std::string(".");
			}
			else
			{
				// success
				std::string ret(buffer);
				free(buffer);
				return ret;
			}
#else
			char *buffer = getcwd(nullptr, 0);
			if (buffer == nullptr)
			{
				// failed
				log::detail::zupply_internal_warn("Unable to get current working directory!");
				return std::string(".");
			}
			else
			{
				// success
				std::string ret(buffer);
				free(buffer);
				return ret;
			}
#endif
		}

		bool path_exists(std::string path, bool considerFile)
		{
#if ZUPPLY_OS_WINDOWS
			DWORD fileType = GetFileAttributesW(utf8_to_wstring(path).c_str());
			if (fileType == INVALID_FILE_ATTRIBUTES) {
				return false;
			}
			return considerFile ? true : ((fileType & FILE_ATTRIBUTE_DIRECTORY) == 0 ? false : true);
#elif ZUPPLY_OS_UNIX
			struct stat st;
			int ret = stat(path.c_str(), &st);
			return considerFile ? (ret == 0) : S_ISDIR(st.st_mode);
#endif
			return false;
		}

		bool is_file(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			DWORD fileType = GetFileAttributesW(utf8_to_wstring(path).c_str());
			if (fileType == INVALID_FILE_ATTRIBUTES || ((fileType & FILE_ATTRIBUTE_DIRECTORY) != 0))
			{
				return false;
			}
			return true;
#elif ZUPPLY_OS_UNIX
			struct stat st;
			if (stat(path.c_str(), &st) != 0) return false;
			if (S_ISDIR(st.st_mode)) return false;
			return true;
#endif
			return false;
		}

		bool is_directory(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			DWORD fileType = GetFileAttributesW(utf8_to_wstring(path).c_str());
			if (fileType == INVALID_FILE_ATTRIBUTES) return false;
			if ((fileType & FILE_ATTRIBUTE_DIRECTORY) != 0) return true;
			return false;
#elif ZUPPLY_OS_UNIX
			struct stat st;
			if (stat(path.c_str(), &st) != 0) return false;
			if (S_ISDIR(st.st_mode)) return true;
			return false;
#endif
			return false;
		}

		bool path_identical(std::string first, std::string second, bool forceCaseSensitve)
		{
#if ZUPPLY_OS_WINDOWS
			if (!forceCaseSensitve)
			{
				return fmt::to_lower_ascii(first) == fmt::to_lower_ascii(second);
			}
			return first == second;
#else
			return first == second;
#endif
		}

		std::string absolute_path(std::string reletivePath)
		{
#if ZUPPLY_OS_WINDOWS
			wchar_t *buffer = nullptr;
			std::wstring widePath = utf8_to_wstring(reletivePath);
			buffer = _wfullpath(buffer, widePath.c_str(), _MAX_PATH);
			if (buffer == nullptr)
			{
				// failed
				log::detail::zupply_internal_warn("Unable to get absolute path for: " + reletivePath + "! Return original.");
				return reletivePath;
			}
			else
			{
				std::wstring ret(buffer);
				free(buffer);
				return wstring_to_utf8(ret);
			}
#elif ZUPPLY_OS_UNIX
			char *buffer = realpath(reletivePath.c_str(), nullptr);
			if (buffer == nullptr)
			{
				// failed
				if (ENOENT == errno)
				{
					// try recover manually
					std::string dirtyPath;
					if (fmt::starts_with(reletivePath, "/"))
					{
						// already an absolute path
						dirtyPath = reletivePath;
					}
					else
					{
						dirtyPath = path_join({ current_working_directory(), reletivePath });
					}
					std::vector<std::string> parts = path_split(dirtyPath);
					std::vector<std::string> ret;
					for (auto i = parts.begin(); i != parts.end(); ++i)
					{
						if (*i == ".") continue;
						if (*i == "..")
						{
							if (ret.size() < 1) throw RuntimeException("Invalid path: " + dirtyPath);
							ret.pop_back();
						}
						else
						{
							ret.push_back(*i);
						}
					}
					std::string tmp = path_join(ret);
					if (!fmt::starts_with(tmp, "/")) tmp = "/" + tmp;
					return tmp;
				}
				//still failed
				log::detail::zupply_internal_warn("Unable to get absolute path for: " + reletivePath + "! Return original.");
				return reletivePath;
			}
			else
			{
				std::string ret(buffer);
				free(buffer);
				return ret;
			}
#endif
			return std::string();
		}

		bool create_directory(std::string path)
		{
#if ZUPPLY_OS_WINDOWS
			std::wstring widePath = utf8_to_wstring(path);
			int ret = _wmkdir(widePath.c_str());
			if (0 == ret) return true;	// success
			if (EEXIST == ret) return true; // already exists
			return false;
#elif ZUPPLY_OS_UNIX
			// read/write/search permissions for owner and group
			// and with read/search permissions for others
			int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
			if (0 == status) return true; // success
			if (EEXIST == errno) return true; // already exists
			return false;
#endif
			return false;
		}

		bool create_directory_recursive(std::string path)
		{
			std::string tmp = absolute_path(path);
			std::string target = tmp;

			while (!is_directory(tmp))
			{
				if (tmp.empty()) return false;	// invalid path
				tmp = absolute_path(tmp + "/../");
			}

			// tmp is the root from where to build
			auto list = path_split(fmt::lstrip(target, tmp));
			for (auto sub : list)
			{
				tmp = path_join({ tmp, sub });
				if (!create_directory(tmp)) break;
			}
			return is_directory(path);
		}

		std::vector<std::string> list_directory(std::string root)
		{
			std::vector<std::string> ret;
			root = os::absolute_path(root);
			if (os::is_file(root))
			{
				// it's a file, not dir
				log::detail::zupply_internal_warn(root + " is a file, not a directory!");
				return ret;
			}
			if (!os::is_directory(root)) return ret;	// not a dir

#if ZUPPLY_OS_WINDOWS
			std::wstring wroot = os::utf8_to_wstring(root);
			wchar_t dirPath[1024];
			wsprintfW(dirPath, L"%s\\*", wroot.c_str());
			WIN32_FIND_DATAW f;
			HANDLE h = FindFirstFileW(dirPath, &f);
			if (h == INVALID_HANDLE_VALUE) { return ret; }

			do
			{
				const wchar_t *name = f.cFileName;
				if (lstrcmpW(name, L".") == 0 || lstrcmpW(name, L"..") == 0) { continue; }
				std::wstring path = wroot + L"\\" + name;
				ret.push_back(os::wstring_to_utf8(path));
			} while (FindNextFileW(h, &f));
			FindClose(h);
			return ret;
#else
			DIR *dir;
			struct dirent *entry;

			if (!(dir = opendir(root.c_str())))
				return ret;
			if (!(entry = readdir(dir)))
				return ret;

			do {
				if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
					continue;
				std::string path = root + "/" + entry->d_name;
				ret.push_back(path);
			} while ((entry = readdir(dir)));
			closedir(dir);
			return ret;
#endif
			return ret;
		}

	}// namespace os

	namespace cds
	{
		bool RWLockable::Counters::is_waiting_for_write() const {
			return writeClaim_ != writeDone_;
		}

		bool RWLockable::Counters::is_waiting_for_read() const {
			return read_ != 0;
		}

		bool RWLockable::Counters::is_my_turn_to_write(Counters const & claim) const {
			return writeDone_ == claim.writeClaim_ - 1;
		}

		bool RWLockable::Counters::want_to_read(RWLockable::Counters * buf) const {
			if (read_ == UINT16_MAX) {
				return false;
			}
			*buf = *this;
			buf->read_ += 1;
			return true;
		}

		bool RWLockable::Counters::want_to_write(RWLockable::Counters * buf) const {
			if (writeClaim_ == UINT8_MAX) {
				return false;
			}
			*buf = *this;
			buf->writeClaim_ += 1;
			return true;
		}

		RWLockable::Counters RWLockable::Counters::done_reading() const {
			Counters c = *this;
			c.read_ -= 1;
			return c;
		}

		RWLockable::Counters RWLockable::Counters::done_writing() const {
			Counters c = *this;
			c.writeDone_ += 1;
			if (c.writeDone_ == UINT8_MAX) {
				c.writeClaim_ = c.writeDone_ = 0;
			}
			return c;
		}

		RWLock RWLockable::lock_for_read() {
			bool written = false;
			do {
				Counters exp = counters_.load(std::memory_order_acquire);
				do {
					if (exp.is_waiting_for_write()) {
						break;
					}
					Counters claim;
					if (!exp.want_to_read(&claim)) {
						break;
					}
					written = counters_.compare_exchange_weak(
						exp, claim,
						std::memory_order_release,
						std::memory_order_acquire
						);
				} while (!written);
				// todo: if (!written) progressive backoff
			} while (!written);
			return RWLock(this, false);
		}

		RWLock RWLockable::lock_for_write() {
			Counters exp = counters_.load(std::memory_order_acquire);
			Counters claim;
			do {
				while (!exp.want_to_write(&claim)) {
					// todo: progressive backoff
					exp = counters_.load(std::memory_order_acquire);
				}
			} while (!counters_.compare_exchange_weak(
				exp, claim,
				std::memory_order_release,
				std::memory_order_acquire
				));
			while (exp.is_waiting_for_read() || !exp.is_my_turn_to_write(claim)) {
				// todo: progressive backoff
				exp = counters_.load(std::memory_order_acquire);
			}
			return RWLock(this, true);
		}

		void RWLockable::unlock_read() {
			Counters exp = counters_.load(std::memory_order_consume);
			Counters des;
			do {
				des = exp.done_reading();
			} while (!counters_.compare_exchange_weak(
				exp, des,
				std::memory_order_release,
				std::memory_order_consume
				));
		}

		void RWLockable::unlock_write() {
			Counters exp = counters_.load(std::memory_order_consume);
			Counters des;
			do {
				des = exp.done_writing();
			} while (!counters_.compare_exchange_weak(
				exp, des,
				std::memory_order_release,
				std::memory_order_consume
				));
		}

		bool RWLockable::is_lock_free() const {
			return counters_.is_lock_free();
		}

		RWLock::RWLock(RWLockable * const lockable, bool const exclusive)
			: lockable_(lockable)
			, lockType_(exclusive ? LockType::write : LockType::read)
		{}

		RWLock::RWLock()
			: lockable_(nullptr)
			, lockType_(LockType::none)
		{}


		RWLock::RWLock(RWLock&& rhs) {
			lockable_ = rhs.lockable_;
			lockType_ = rhs.lockType_;
			rhs.lockable_ = nullptr;
			rhs.lockType_ = LockType::none;
		}

		RWLock& RWLock::operator =(RWLock&& rhs) {
			std::swap(lockable_, rhs.lockable_);
			std::swap(lockType_, rhs.lockType_);
			return *this;
		}


		RWLock::~RWLock() {
			if (lockable_ == nullptr) {
				return;
			}
			switch (lockType_) {
			case LockType::read:
				lockable_->unlock_read();
				break;
			case LockType::write:
				lockable_->unlock_write();
				break;
			default:
				// do nothing
				break;
			}
		}

		void RWLock::unlock() {
			(*this) = RWLock();
			// emptyLock's dtor will now activate
		}

		RWLock::LockType RWLock::get_lock_type() const {
			return lockType_;
		}
	} // namespace cds

	namespace cfg
	{

		CfgParser::CfgParser(std::string filename) : ln_(0)
		{
			os::ifstream_open(stream_, filename, std::ios::in | std::ios::binary);
			if (!stream_.is_open())
			{
				throw IOException("Failed to open config file: " + filename);
			}
			pstream_ = &stream_;
			parse(root_);
		}

		void CfgParser::error_handler(std::string msg)
		{
			throw RuntimeException("Parser failed at line<" + std::to_string(ln_) + "> " + line_ + "\n" + msg);
		}

		CfgLevel* CfgParser::parse_section(std::string sline, CfgLevel* lvl)
		{
			if (sline.empty()) return lvl;
			if (sline.back() == '.') error_handler("section ends with .");
			auto pos = sline.find('.');
			std::string currentSection;
			std::string nextSection;
			if (std::string::npos == pos)
			{
				currentSection = sline;
			}
			else
			{
				currentSection = sline.substr(0, pos);
				nextSection = sline.substr(pos + 1);
			}
			CfgLevel *l = &lvl->sections[currentSection];
			l->depth = lvl->depth + 1;
			l->parent = lvl;
			l->prefix = lvl->prefix + currentSection + ".";
			return parse_section(nextSection, l);
		}

		CfgLevel* CfgParser::parse_key_section(std::string& vline, CfgLevel *lvl)
		{
			if (vline.back() == '.') error_handler("key ends with .");
			auto pos = vline.find('.');
			if (std::string::npos == pos) return lvl;
			std::string current = vline.substr(0, pos);
			vline = vline.substr(pos + 1);
			CfgLevel *l = &lvl->sections[current];
			l->depth = lvl->depth + 1;
			l->parent = lvl;
			l->prefix = lvl->prefix + current + ".";
			return parse_key_section(vline, l);
		}

		std::string CfgParser::split_key_value(std::string& line)
		{
			auto pos1 = line.find_first_of(':');
			auto pos2 = line.find_first_of('=');
			if (std::string::npos == pos1 && std::string::npos == pos2)
			{
				throw RuntimeException("':' or '=' expected in key-value pair, none found.");
			}
			auto pos = (std::min)(pos1, pos2);
			std::string key = fmt::trim(std::string(line.substr(0, pos)));
			line = fmt::trim(std::string(line.substr(pos + 1)));
			return key;
		}

		void CfgParser::parse(CfgLevel& lvl)
		{
			CfgLevel *ls = &lvl;
			while (std::getline(*pstream_, line_))
			{
				++ln_;
				line_ = fmt::rskip_all(line_, "#");
				line_ = fmt::rskip_all(line_, ";");
				line_ = fmt::trim(line_);
				if (line_.empty()) continue;
				if (line_[0] == '[')
				{
					// start a section
					if (line_.back() != ']')
					{
						error_handler("missing right ]");
					}
					ls = &lvl;
					ls = parse_section(std::string(line_.substr(1, line_.length() - 2)), ls);
				}
				else
				{
					// parse section inline with key
					std::string key = split_key_value(line_);
					CfgLevel *lv = ls;
					lv = parse_key_section(key, lv);
					if (lv->values.find(key) != lv->values.end())
					{
						error_handler("duplicate key");
					}
					lv->values[key] = line_;
				}
			}
		}

		ArgOption::ArgOption(char shortKey, std::string longKey) :
			shortKey_(shortKey), longKey_(longKey),
			type_(""), default_(""), required_(false),
			min_(0), max_(1), once_(false), count_(0),
			val_(""), size_(0)
		{}

		ArgOption::ArgOption(char shortKey) : ArgOption(shortKey, "")
		{}

		ArgOption::ArgOption(std::string longKey) : ArgOption(-1, longKey)
		{}

		ArgOption& ArgOption::call(std::function<void()> todo)
		{
			callback_.push_back(todo);
			return *this;
		}

		ArgOption& ArgOption::call(std::function<void()> todo, std::function<void()> otherwise)
		{
			callback_.push_back(todo);
			othercall_.push_back(otherwise);
			return *this;
		}

		ArgOption& ArgOption::set_help(std::string helpInfo)
		{
			help_ = helpInfo;
			return *this;
		}

		ArgOption& ArgOption::require(bool require)
		{
			required_ = require;
			return *this;
		}

		ArgOption& ArgOption::set_once(bool onlyOnce)
		{
			once_ = onlyOnce;
			return *this;
		}

		ArgOption& ArgOption::set_type(std::string type)
		{
			type_ = type;
			return *this;
		}

		ArgOption& ArgOption::set_min(int minCount)
		{
			min_ = minCount > 0 ? minCount : 0;
			return *this;
		}

		ArgOption& ArgOption::set_max(int maxCount)
		{
			max_ = maxCount;
			if (max_ < 0) max_ = -1;
			return *this;
		}

		Value ArgOption::get_value()
		{
			return val_;
		}

		int ArgOption::get_count()
		{
			return count_;
		}

		std::string ArgOption::get_help()
		{
			// target: '-i, --input=FILE		this is an example input(default: abc.txt)'
			const std::size_t alignment = 26;
			std::string ret;

			std::string type;
			for(char c: type_)
			{
				if(c == '\n')
				{
					type += ' ';
				}
				else
				{
					type += c;
				}
			}

			// place holders
			if (shortKey_ == -1 && longKey_.empty() && min_ > 0)
			{
				ret = "<" + type + ">";
			}
			else
			{
				if (shortKey_ != -1)
				{
					ret += "-";
					ret.push_back(shortKey_);
				}
				if (!longKey_.empty())
				{
					if (!ret.empty()) ret += ", ";
					ret += "--";
					ret += longKey_;
				}
				if (!type.empty())
				{
					ret.push_back('=');
					ret += type;
				}
			}

			if (ret.size() < alignment)
			{
				while (ret.size() < alignment)
					ret.push_back(' ');
			}
			else
			{
				// super long option, then create a new line
				ret.push_back('\n');
				std::string tmp;
				tmp.resize(alignment);
				ret += tmp;
			}
			ret += help_;
			if (!default_.empty())
			{
				ret += "(default: ";
				ret += default_;
				ret += ")";
			}
			return ret;
		}

		std::string ArgOption::get_short_help()
		{
			// [-a=<DOUBLE>] [--long=<INT> [<INT>]]...
			std::string ret = "-";
			if (shortKey_ != -1)
			{
				ret.push_back(shortKey_);
			}
			else if (!longKey_.empty())
			{
				ret += "-" + longKey_;
			}
			else
			{
				ret = "<" + type_ + ">";
				return ret;
			}

			// type info
			std::string st = "<" + type_ + ">";
			std::vector<std::string> stvec;
			if(type_.find('\n') != std::string::npos)
			{
				st = "";
				for(char c: type_)
				{
					if(c == '\n') {
						stvec.emplace_back("<" + st + ">");
						st = "";
					}
					else
					{
						st += c;
					}
				}
				if(!st.empty())
				{
					stvec.emplace_back("<" + st + ">");
				}
				while(stvec.size() <= min_)
				{
					stvec.emplace_back("<>");
				}
				st = "<" + type_ + ">";
			}
			if (min_ > 0)
			{
				if (required_) ret.push_back('=');
				else ret.push_back(' ');
				if(stvec.empty())
				{
					ret += st;
				}
				else
				{
					ret += stvec.at(0);
				}
				for (int i = 2; i < min_ + 1; ++i)
				{
					ret.push_back(' ');
					if(stvec.empty())
					{
						ret += st;
					}
					else
					{
						ret += stvec.at(i - 1);
					}
				}
				if (max_ == -1 || max_ > min_)
				{
					if(stvec.empty())
					{
						ret += " {" + st + "}...";
					}
					else
					{
						for(int i = min_ + 1; i < stvec.size(); ++i)
						{
							ret += stvec.at(i - 1);
						}
						if(stvec.size() < max_ + 1)
						{
							ret += " {" + stvec.back() + "}...";
						}
					}
				}
			}

			if (!required_)
			{
				ret = "[" + ret + "]";
			}
			return ret;
		}

		ArgParser::ArgParser() : info_({ "program", "?" })
		{
			add_opt_internal(-1, "").set_max(0);	// reserve for help
			add_opt_internal(-1, "").set_max(0);	// reserve for version
		}

		void ArgParser::add_info(std::string info)
		{
			info_.push_back(info);
		}

		std::vector<Value> ArgParser::arguments() const
		{
			return args_;
		}

		void ArgParser::register_keys(char shortKey, std::string longKey, std::size_t pos)
		{
			if (shortKey != -1)
			{
				if (shortKey < 32 || shortKey > 126)
				{
					// unsupported ASCII characters
					throw ArgException("Unsupported ASCII character: " + std::string(1, shortKey));
				}
				auto opt = shortKeys_.find(shortKey);
				if (opt != shortKeys_.end())
				{
					std::string tmp("Short key: ");
					tmp.push_back(shortKey);
					throw ArgException(tmp + " already occupied -> " + options_[opt->second].get_help());
				}
				// register short key
				shortKeys_[shortKey] = pos;
			}

			if (!longKey.empty())
			{
				auto opt = longKeys_.find(longKey);
				if (opt != longKeys_.end())
				{
					throw ArgException("Long key: " + longKey + " already occupied -> " + options_[opt->second].get_help());
				}
				// register long key
				longKeys_[longKey] = pos;
			}
		}

		ArgOption& ArgParser::add_opt_internal(char shortKey, std::string longKey)
		{
			options_.push_back(ArgOption(shortKey, longKey));
			if (shortKey != -1 || (!longKey.empty()))
			{
				register_keys(shortKey, longKey, options_.size() - 1);
			}
			return options_.back();
		}

		ArgOption& ArgParser::add_opt(char shortKey, std::string longKey)
		{
			return add_opt_internal(shortKey, longKey);
		}

		ArgOption& ArgParser::add_opt(char shortKey)
		{
			return add_opt_internal(shortKey, "");
		}

		ArgOption& ArgParser::add_opt(std::string longKey)
		{
			return add_opt_internal(-1, longKey);
		}

		void ArgParser::add_opt_help(char shortKey, std::string longKey, std::string help)
		{
			auto& opt = options_[0];	// options_[0] reserved for help
			register_keys(shortKey, longKey, 0);
			opt.shortKey_ = shortKey;
			opt.longKey_ = longKey;
			opt.set_help(help)
				.call([this]{std::cout << "\n" << this->get_help() << std::endl; std::exit(0); })
				.set_min(0).set_max(0);
		}

		void ArgParser::add_opt_version(char shortKey, std::string longKey, std::string version, std::string help)
		{
			info_[1] = version;			// info_[1] reserved for version string
			auto& opt = options_[1];	// options_[1] reserved for version option
			register_keys(shortKey, longKey, 1);
			opt.shortKey_ = shortKey;
			opt.longKey_ = longKey;
			opt.set_help(help)
				.call([this]{std::cout << this->version() << std::endl; std::exit(0); })
				.set_min(0).set_max(0);
		}

		ArgOption& ArgParser::add_opt_flag(char shortKey, std::string longKey, std::string help, bool* dst)
		{
			auto& opt = add_opt(shortKey, longKey).set_help(help).set_min(0).set_max(0);
			if (nullptr != dst)
			{
				opt.call([dst]{*dst = true; }, [dst]{*dst = false; });
			}
			return opt;
		}

		ArgParser::Type ArgParser::check_type(std::string opt)
		{
			if (opt.length() == 1 && opt[0] == '-') return Type::INVALID;
			if (opt.length() == 2 && opt == "--") return Type::INVALID;
			if (opt[0] == '-' && opt[1] == '-')
			{
				if (opt.length() < 3) return Type::INVALID;
				return Type::LONG_KEY;
			}
			else if (opt[0] == '-')
			{
				return Type::SHORT_KEY;
			}
			else
			{
				return Type::ARGUMENT;
			}
		}

		ArgParser::ArgQueue ArgParser::pretty_arguments(int argc, char** argv)
		{
			ArgQueue queue;
			for (int p = 1; p < argc; ++p)
			{
				std::string opt(argv[p]);
				Type type = check_type(opt);
				if (Type::SHORT_KEY == type)
				{
					// parse options like -abc=somevalue
					auto lr = fmt::split_first_occurance(opt, '=');
					auto first = lr.first; // -abc
					for (std::size_t i = 1; i < first.length(); ++i)
					{
						// put a b c into queue
						queue.push_back(std::make_pair("-" + first.substr(i, 1), Type::SHORT_KEY));
					}
					if (!lr.second.empty())
					{
						// put somevalue into queue
						queue.push_back(std::make_pair(lr.second, Type::ARGUMENT));
					}
				}
				else if (Type::LONG_KEY == type)
				{
					// parse long option like: --long=somevalue
					auto lr = fmt::split_first_occurance(opt, '=');
					queue.push_back(std::make_pair(lr.first, Type::LONG_KEY));
					if (!lr.second.empty())
					{
						queue.push_back(std::make_pair(lr.second, Type::ARGUMENT));
					}
				}
				else if (Type::ARGUMENT == type)
				{
					if (opt.length() > 2)
					{
						if (opt.front() == '\'' && opt.back() == '\'')
						{
							opt = opt.substr(1, opt.length() - 2);
						}
					}
					queue.push_back(std::make_pair(opt, Type::ARGUMENT));
				}
				else
				{
					queue.push_back(std::make_pair(opt, Type::INVALID));
				}
			}
			return queue;
		}

		void ArgParser::error_option(std::string opt, std::string msg)
		{
			errors_.push_back("Error parsing option: " + opt + " " + msg);
		}

		void ArgParser::parse_option(ArgOption* ptr)
		{
			++ptr->count_;
		}

		void ArgParser::parse_value(ArgOption* ptr, const std::string& value)
		{
			if (ptr == nullptr) args_.push_back(value);
			else
			{
				if (ptr->max_ < 0 || ptr->size_ < ptr->max_)
				{
					// boundless or not yet reached maximum
					ptr->val_ = ptr->val_.str() + value + " ";
					++ptr->size_;
				}
				else
				{
					args_.push_back(value);
					ptr = nullptr;
				}
			}
		}

		void ArgParser::parse(int argc, char** argv, bool ignoreUnknown)
		{
			if (argc < 1)
			{
				errors_.push_back("Argc < 1");
				return;
			}

			// 0. prog name
			info_[0] = os::path_split_filename(std::string(argv[0]));

			// 1. make prettier argument queue
			auto queue = pretty_arguments(argc, argv);

			// 2. parse one by one
			ArgOption *opt = nullptr;
			for (auto q : queue)
			{
				if (Type::SHORT_KEY == q.second)
				{
					auto iter = shortKeys_.find(q.first[1]);
					if (iter == shortKeys_.end())
					{
						if (!ignoreUnknown) error_option(q.first, "unknown option.");
						continue;
					}
					opt = &options_[iter->second];
					parse_option(opt);
				}
				else if (Type::LONG_KEY == q.second)
				{
					auto iter = longKeys_.find(q.first.substr(2));
					if (iter == longKeys_.end())
					{
						if (!ignoreUnknown) error_option(q.first, "unknown option.");
						continue;
					}
					opt = &options_[iter->second];
					parse_option(opt);
				}
				else if (Type::ARGUMENT == q.second)
				{
					parse_value(opt, q.first);
				}
				else
				{
					error_option(q.first, "invalid option.");
				}
			}

			// 3. placeholders
			for (auto o = options_.begin(); o != options_.end(); ++o)
			{
				if (o->shortKey_ == -1 && o->longKey_.empty())
				{
					int n = o->max_;
					while (n > 0 && args_.size() > 0)
					{
						o->val_ = o->val_.str() + " " + args_[0].str();
						++o->count_;
						--n;
						args_.erase(args_.begin());
					}
					o->val_ = fmt::trim(o->val_.str());
					if (args_.empty()) break;
				}
			}

			// 4. callbacks
			for (auto o : options_)
			{
				if (o.count_ > 0)
				{
					// callback functions
					for (auto call : o.callback_)
					{
						call();
					}
				}
				else
				{
					// not found, call the other functions
					for (auto othercall : o.othercall_)
					{
						othercall();
					}
				}
			}


			// 5. check errors
			for (auto o : options_)
			{
				if (o.required_ && (o.count_ < 1))
				{
					errors_.push_back("[ArgParser Error]: Required option not found: " + o.get_short_help());
				}
				if (o.count_ >= 1 && o.min_ > o.size_)
				{
					errors_.push_back("[ArgParser Error]:" + o.get_short_help() + " needs at least " + std::to_string(o.min_) + " arguments.");
				}
				if (o.count_ > 1 && o.once_)
				{
					errors_.push_back("[ArgParser Error]:" + o.get_short_help() + " limited to be called only once.");
				}
			}
		}

		std::string ArgParser::get_help()
		{
			std::string ret("Usage: ");
			ret += info_[0];
			std::string usageLine;

			// single line options list
			for (auto opt : options_)
			{
				if (!opt.required_ && opt.max_ == 0 && opt.shortKey_ != -1)
				{
					// put optional short flags together [-abcdefg...]
					usageLine.push_back(opt.shortKey_);
				}
			}
			if (!usageLine.empty())
			{
				usageLine = " [-" + usageLine + "]";
			}

			// options that take arguments
			for (auto opt : options_)
			{
				if (!opt.required_ && opt.max_ == 0) continue;
				std::string tmp = " " + opt.get_short_help();
				if (!tmp.empty()) usageLine += tmp;
			}

			ret += " " + usageLine + "\n";

			for (std::size_t i = 2; i < info_.size(); ++i)
			{
				ret += "  " + info_[i] + "\n";
			}

			// required options first
			ret += "\n  Required options:\n";
			for (auto opt : options_)
			{
				if (opt.required_)
				{
					std::string tmpLine = "  " + opt.get_help() + "\n";
					if (fmt::trim(tmpLine).empty()) continue;
					ret += tmpLine;
				}
			}
			// optional options
			ret += "\n  Optional options:\n";
			for (auto opt : options_)
			{
				if (!opt.required_)
				{
					std::string tmpLine = "  " + opt.get_help() + "\n";
					if (fmt::trim(tmpLine).empty()) continue;
					ret += tmpLine;
				}
			}
			return ret;
		}

		std::string ArgParser::get_error()
		{
			std::string ret;
			for (auto e : errors_)
			{
				ret += e;
				ret.push_back('\n');
			}
			return ret;
		}

		int	ArgParser::count(char shortKey)
		{
			auto iter = shortKeys_.find(shortKey);
			return iter == shortKeys_.end() ? 0 : options_[iter->second].count_;
		}

		int ArgParser::count(std::string longKey)
		{
			auto iter = longKeys_.find(longKey);
			return iter == longKeys_.end() ? 0 : options_[iter->second].count_;
		}

		Value ArgParser::operator[](const std::string& longKey)
		{
			auto iter = longKeys_.find(longKey);
			if (iter == longKeys_.end())
			{
				return Value();
			}
			else
			{
				return options_[iter->second].val_;
			}
		}

		Value ArgParser::operator[](const char shortKey)
		{
			auto iter = shortKeys_.find(shortKey);
			if (iter == shortKeys_.end())
			{
				return Value();
			}
			else
			{
				return options_[iter->second].val_;
			}
		}

	} // namespace cfg

	namespace log
	{
		ProgBar::ProgBar(unsigned range, std::string info)
			: ss_(nullptr), info_(info)
		{
			range_ = range;
			pos_ = 0;
			running_ = false;
			timer_.reset();
			sprintf(rate_, "%.1f/sec", 0.0);
			start();
		}

		ProgBar::~ProgBar()
		{
			stop();
		}

		void ProgBar::step(unsigned size)
		{
			pos_ += size;
			if (pos_ >= range_)
			{
				stop();
			}
			calc_rate(size);
			timer_.reset();
		}

		void ProgBar::calc_rate(unsigned size)
		{
			double interval = timer_.elapsed_sec_double();
			if (interval < 1e-12) interval = 1e-12;
			double rate = size / interval;
			if (rate > 1073741824)
			{
				sprintf(rate_, "%.1e/s", rate);
			}
			else if (rate > 1048576)
			{
				sprintf(rate_, "%.1fM/sec", rate / 1048576);
			}
			else if (rate > 1024)
			{
				sprintf(rate_, "%.1fK/sec", rate / 1024);
			}
			else if (rate > 0.1)
			{
				sprintf(rate_, "%.1f/sec", rate);
			}
			else if (rate * 60 > 1)
			{
				sprintf(rate_, "%.1f/min", rate * 60);
			}
			else if (rate * 3600 > 1)
			{
				sprintf(rate_, "%.1f/sec", rate * 3600);
			}
			else
			{
				sprintf(rate_, "%.1e/s", rate);
			}
		}

		void ProgBar::start()
		{
			if (os::is_atty())
			{
				oldCout_ = std::cout.rdbuf(buffer_.rdbuf());
				oldCerr_ = std::cerr.rdbuf(buffer_.rdbuf());
				ss_.rdbuf(oldCout_);
				//ss_ << std::endl;
				running_ = true;
				worker_ = std::thread([this]{ this->bg_work(); });
			}
		}

		void ProgBar::stop()
		{
			if (running_)
			{
				running_ = false;
				worker_.join();	// join worker thread
				draw();
				std::cout.rdbuf(oldCout_);
				std::cerr.rdbuf(oldCerr_);
				std::cout << std::endl;
			}
		}

		void ProgBar::draw()
		{
			Size sz = os::console_size();
			// clear line
			ss_ << "\r";
			for (int i = 1; i < sz.width; ++i)
			{
				ss_ << " ";
			}
			ss_ << "\r";
			// flush stored messages
			std::string buf = buffer_.str();
			if (!buf.empty())
			{
				ss_ << buf;
				if (buf.back() != '\n')
				{
					ss_ << "\n";
				}
				try
				{
					buffer_.str(std::string());
				}
				catch (...)
				{
					// suppress the exception as a hack
				}
			}

			const int reserved = 21;	// leave some space for info
			int available = sz.width - reserved - static_cast<int>(info_.size()) - 2;
			if (available > 10)
			{
				int cnt = 0;
				int len = static_cast<int>(pos_ / static_cast<double>(range_)* available) - 1;
				ss_ << info_ << "[";
				for (int i = 0; i < len; ++i)
				{
					ss_ << "=";
					++cnt;
				}
				if (len >= 0)
				{
					ss_ << ">";
					++cnt;
				}
				for (int i = cnt; i < available; ++i)
				{
					ss_ << " ";
				}
				ss_ << "] ";
				float perc = 100.f * pos_ / range_;
				ss_ << std::fixed << std::setprecision(1) << perc;
				ss_ << "% (";
				ss_ << std::string(rate_);
				ss_ << ")";
				ss_.flush();
			}

		}

		void ProgBar::bg_work()
		{
			while (running_)
			{
				draw();
				time::sleep(66);
			}
		}

		namespace detail
		{
			static bool signal_handler_set = false;

			static void terminate_signal_handler(int) {
				auto loggers = LoggerRegistry::instance().get_all();
				for (auto logger : loggers) {
					logger->detach_all_sinks();
				}
				exit(0);
			}

			LoggerRegistry& LoggerRegistry::instance()
			{
				static LoggerRegistry sInstance;
				if (!signal_handler_set) {
					signal(SIGINT, terminate_signal_handler);
					signal_handler_set = true;
				}
				return sInstance;
			}

			LoggerPtr LoggerRegistry::create(const std::string &name)
			{
				auto ptr = new_registry(name);
				if (!ptr)
				{
					throw RuntimeException("Logger with name: " + name + " already existed.");
				}
				return ptr;
			}

			LoggerPtr LoggerRegistry::ensure_get(std::string &name)
			{
				LoggerPtr ptr;
				if (loggers_.get(name, ptr))
				{
					return ptr;
				}

				do
				{
					ptr = new_registry(name);
				} while (ptr == nullptr);

				return ptr;
			}

			LoggerPtr LoggerRegistry::get(std::string &name)
			{
				LoggerPtr ptr;
				if (loggers_.get(name, ptr)) return ptr;
				return nullptr;
			}

			std::vector<LoggerPtr> LoggerRegistry::get_all()
			{
				std::vector<LoggerPtr> list;
				auto loggers = loggers_.snapshot();
				for (auto logger : loggers)
				{
					list.push_back(logger.second);
				}
				return list;
			}

			void LoggerRegistry::drop(const std::string &name)
			{
				loggers_.erase(name);
			}

			void LoggerRegistry::drop_all()
			{
				loggers_.clear();
			}

			void LoggerRegistry::lock()
			{
				lock_ = true;
			}

			void LoggerRegistry::unlock()
			{
				lock_ = false;
			}

			bool LoggerRegistry::is_locked() const
			{
				return lock_;
			}

			LoggerPtr LoggerRegistry::new_registry(const std::string &name)
			{
				LoggerPtr newLogger = std::make_shared<Logger>(name);
				auto defaultSinkList = LogConfig::instance().sink_list();
				for (auto sinkname : defaultSinkList)
				{
					SinkPtr psink = nullptr;
					if (sinkname == consts::kStdoutSinkName)
					{
						psink = new_stdout_sink();
					}
					else if (sinkname == consts::kStderrSinkName)
					{
						psink = new_stderr_sink();
					}
					else
					{
						psink = get_sink(sinkname);
					}
					if (psink) newLogger->attach_sink(psink);
				}
				if (loggers_.insert(name, newLogger))
				{
					return newLogger;
				}
				return nullptr;
			}

			RotateFileSink::RotateFileSink(const std::string filename, std::size_t maxSizeInByte, bool backup)
				:maxSizeInByte_(maxSizeInByte), backup_(backup)
			{
				if (backup_)
				{
					back_up(filename);
				}
				fileEditor_.open(filename, true);
				currentSize_ = 0;
			}

			void RotateFileSink::back_up(std::string oldFile)
			{
				std::string backupName = os::path_append_basename(oldFile,
					time::DateTime::local_time().to_string("_%y-%m-%d_%H-%M-%S-%frac"));
				os::rename(oldFile, backupName);
			}

			void RotateFileSink::rotate()
			{
				std::lock_guard<std::mutex> lock(mutex_);
				// check again in case other thread
				// just waited for this operation
				if (currentSize_ > maxSizeInByte_)
				{
					if (backup_)
					{
						fileEditor_.close();
						back_up(fileEditor_.filename());
						fileEditor_.open(true);
					}
					else
					{
						fileEditor_.reopen(true);
					}
					currentSize_ = 0;
				}
			}

			void sink_list_revise(std::vector<std::string> &list, std::map<std::string, std::string> &map)
			{
				for (auto m : map)
				{
					for (auto l = list.begin(); l != list.end(); ++l)
					{
						if (m.first == *l)
						{
							l = list.erase(l);
							l = list.insert(l, m.second);
						}
					}
				}
			}

			void config_loggers_from_section(cfg::CfgLevel::section_map_t &section, std::map<std::string, std::string> &map)
			{
				for (auto loggerSec : section)
				{
					for (auto value : loggerSec.second.values)
					{
						LoggerPtr logger = nullptr;
						if (consts::kConfigLevelsSpecifier == value.first)
						{
							int mask = level_mask_from_string(value.second.str());
							if (!logger) logger = get_logger(loggerSec.first, true);
							logger->set_level_mask(mask);
						}
						else if (consts::kConfigSinkListSpecifier == value.first)
						{
							auto list = fmt::split_whitespace(value.second.str());
							if (list.empty()) continue;
							sink_list_revise(list, map);
							if (!logger) logger = get_logger(loggerSec.first, true);
							logger->detach_all_sinks();
							logger->attach_sink_list(list);
						}
						else
						{
							zupply_internal_warn("Unrecognized configuration key: " + value.first);
						}
					}
				}
			}

			LoggerPtr get_hidden_logger()
			{
				auto hlogger = get_logger("hidden", false);
				if (!hlogger)
				{
					hlogger = get_logger("hidden", true);
					hlogger->set_level_mask(0);
				}
				return hlogger;
			}

			std::map<std::string, std::string> config_sinks_from_section(cfg::CfgLevel::section_map_t &section)
			{
				std::map<std::string, std::string> sinkMap;
				for (auto sinkSec : section)
				{
					std::string type;
					std::string filename;
					std::string fmt;
					std::string levelStr;
					SinkPtr sink = nullptr;

					for (auto value : sinkSec.second.values)
					{
						// entries
						if (consts::kConfigSinkTypeSpecifier == value.first)
						{
							type = value.second.str();
						}
						else if (consts::kConfigSinkFilenameSpecifier == value.first)
						{
							filename = value.second.str();
						}
						else if (consts::kConfigFormatSpecifier == value.first)
						{
							fmt = value.second.str();
						}
						else if (consts::kConfigLevelsSpecifier == value.first)
						{
							levelStr = value.second.str();
						}
						else
						{
							zupply_internal_warn("Unrecognized config key entry: " + value.first);
						}
					}

					// sink
					if (type.empty()) throw RuntimeException("No suitable type specified for sink: " + sinkSec.first);
					if (type == consts::kStdoutSinkName)
					{
						sink = new_stdout_sink();
					}
					else if (type == consts::kStderrSinkName)
					{
						sink = new_stderr_sink();
					}
					else
					{
						if (filename.empty()) throw RuntimeException("No name specified for sink: " + sinkSec.first);
						if (type == consts::kOstreamSinkType)
						{
							zupply_internal_warn("Currently do not support init ostream logger from config file.");
						}
						else if (type == consts::kSimplefileSinkType)
						{
							sink = new_simple_file_sink(filename);
							get_hidden_logger()->attach_sink(sink);
						}
						else if (type == consts::kRotatefileSinkType)
						{
							sink = new_rotate_file_sink(filename);
							get_hidden_logger()->attach_sink(sink);
						}
						else
						{
							zupply_internal_warn("Unrecognized sink type: " + type);
						}
					}
					if (sink)
					{
						if (!fmt.empty()) sink->set_format(fmt);
						if (!levelStr.empty())
						{
							int mask = level_mask_from_string(levelStr);
							sink->set_level_mask(mask);
						}
						if (!get_hidden_logger()->get_sink(sink->name()))
						{
							get_hidden_logger()->attach_sink(sink);
						}
						sinkMap[sinkSec.first] = sink->name();
					}
				}
				return sinkMap;
			}

			void zupply_internal_warn(std::string msg)
			{
				auto zlog = get_logger(consts::kZupplyInternalLoggerName, true);
				zlog->warn() << msg;
			}

			void zupply_internal_error(std::string msg)
			{
				auto zlog = get_logger(consts::kZupplyInternalLoggerName, true);
				zlog->error() << msg;
			}
		} // namespace log::detail

		LogConfig::LogConfig()
		{
			// Default configurations
			sinkList_.set({ std::string(consts::kStdoutSinkName), std::string(consts::kStderrSinkName) });	//!< attach console by default
#ifdef NDEBUG
			logLevelMask_ = 0x3C;	//!< 0x3C->b111100: no debug, no trace
#else
			logLevelMask_ = 0x3E;	//!< 0x3E->b111110: debug, no trace
#endif
			format_.set(std::string(consts::kDefaultLoggerFormat));
			datetimeFormat_.set(std::string(consts::kDefaultLoggerDatetimeFormat));
		}

		LogConfig& LogConfig::instance()
		{
			static LogConfig instance_;
			return instance_;
		}

		void LogConfig::set_default_format(std::string format)
		{
			LogConfig::instance().set_format(format);
		}

		void LogConfig::set_default_datetime_format(std::string dateFormat)
		{
			LogConfig::instance().set_datetime_format(dateFormat);
		}

		void LogConfig::set_default_sink_list(std::vector<std::string> list)
		{
			LogConfig::instance().set_sink_list(list);
		}

		void LogConfig::set_default_level_mask(int levelMask)
		{
			LogConfig::instance().set_log_level_mask(levelMask);
		}

		std::vector<std::string> LogConfig::sink_list()
		{
			return sinkList_.get();
		}

		void LogConfig::set_sink_list(std::vector<std::string> &list)
		{
			sinkList_.set(list);
		}

		int LogConfig::log_level_mask()
		{
			return logLevelMask_;
		}

		void LogConfig::set_log_level_mask(int newMask)
		{
			logLevelMask_ = newMask;
		}

		std::string LogConfig::format()
		{
			return format_.get();
		}

		void LogConfig::set_format(std::string newFormat)
		{
			format_.set(newFormat);
		}

		std::string LogConfig::datetime_format()
		{
			return datetimeFormat_.get();
		}

		void LogConfig::set_datetime_format(std::string newDatetimeFormat)
		{
			datetimeFormat_.set(newDatetimeFormat);
		}

		// logger.info() << ".." call  style
		detail::LineLogger Logger::trace()
		{
			return log_if_enabled(LogLevels::trace);
		}
		detail::LineLogger Logger::debug()
		{
			return log_if_enabled(LogLevels::debug);
		}
		detail::LineLogger Logger::info()
		{
			return log_if_enabled(LogLevels::info);
		}
		detail::LineLogger Logger::warn()
		{
			return log_if_enabled(LogLevels::warn);
		}
		detail::LineLogger Logger::error()
		{
			return log_if_enabled(LogLevels::error);
		}
		detail::LineLogger Logger::fatal()
		{
			return log_if_enabled(LogLevels::fatal);
		}

		SinkPtr Logger::get_sink(std::string name)
		{
			SinkPtr ptr;
			if (sinks_.get(name, ptr))
			{
				return ptr;
			}
			return nullptr;
		}

		void Logger::attach_sink(SinkPtr sink)
		{
			if (!sinks_.insert(sink->name(), sink))
			{
				throw RuntimeException("Sink with name: " + sink->name() + " already attached to logger: " + name_);
			}
		}

		void Logger::detach_sink(SinkPtr sink)
		{
			sinks_.erase(sink->name());
		}

		void Logger::detach_all_sinks()
		{
			sinks_.clear();
		}

		void Logger::log_msg(detail::LogMessage msg)
		{
			auto sinks = sinks_.snapshot();
			for (auto s : sinks)
			{
				s.second->log(msg);
			}
		}

		std::string Logger::to_string()
		{
			std::string str(name() + ": " + level_mask_to_string(levelMask_));
			str += "\n{\n";
			auto sinkmap = sinks_.snapshot();
			for (auto sink : sinkmap)
			{
				str += sink.second->to_string() + "\n";
			}
			str += "}";
			return str;
		}

		std::string level_mask_to_string(int levelMask)
		{
			std::string str("<|");
			for (int i = 0; i < LogLevels::off; ++i)
			{
				if (level_should_log(levelMask, static_cast<LogLevels>(i)))
				{
					str += consts::kLevelNames[i];
					str += "|";
				}
			}
			return str + ">";
		}

		LogLevels level_from_str(std::string level)
		{
			std::string upperLevel = fmt::to_upper_ascii(level);
			for (int i = 0; i < LogLevels::off; ++i)
			{
				if (upperLevel == consts::kLevelNames[i])
				{
					return static_cast<LogLevels>(i);
				}
			}
			return LogLevels::off;
		}

		int level_mask_from_string(std::string levels)
		{
			int mask = 0;
			auto levelList = fmt::split_whitespace(levels);
			for (auto lvl : levelList)
			{
				auto l = level_from_str(lvl);
				mask |= 1 << static_cast<int>(l);
			}
			return mask & LogLevels::sentinel;
		}

		LoggerPtr get_logger(std::string name, bool createIfNotExists)
		{
			if (createIfNotExists)
			{
				return detail::LoggerRegistry::instance().ensure_get(name);
			}
			else
			{
				return detail::LoggerRegistry::instance().get(name);
			}
		}

		SinkPtr new_stdout_sink()
		{
			return detail::StdoutSink::instance();
		}

		SinkPtr new_stderr_sink()
		{
			return detail::StderrSink::instance();
		}

		void Logger::attach_console()
		{
			sinks_.insert(std::string(consts::kStdoutSinkName), new_stdout_sink());
			sinks_.insert(std::string(consts::kStderrSinkName), new_stderr_sink());
		}

		void Logger::detach_console()
		{
			sinks_.erase(std::string(consts::kStdoutSinkName));
			sinks_.erase(std::string(consts::kStderrSinkName));
		}

		SinkPtr get_sink(std::string name)
		{
			SinkPtr psink = nullptr;
			auto loggers = detail::LoggerRegistry::instance().get_all();
			for (auto logger : loggers)
			{
				psink = logger->get_sink(name);
				if (psink)
				{
					return psink;
				}
			}
			return nullptr;
		}

		void dump_loggers(std::ostream &out)
		{
			auto loggers = detail::LoggerRegistry::instance().get_all();
			out << "{\n";
			for (auto logger : loggers)
			{
				out << logger->to_string() << "\n";
			}
			out << "}" << std::endl;
		}

		SinkPtr new_ostream_sink(std::ostream &stream, std::string name, bool forceFlush)
		{
			auto sinkptr = get_sink(name);
			if (sinkptr)
			{
				throw RuntimeException(name + " already holded by another sink\n" + sinkptr->to_string());
			}
			return std::make_shared<detail::OStreamSink>(stream, name.c_str(), forceFlush);
		}

		SinkPtr new_simple_file_sink(std::string filename, bool truncate)
		{
			auto sinkptr = get_sink(os::absolute_path(filename));
			if (sinkptr)
			{
				throw RuntimeException("File: " + filename + " already holded by another sink!\n" + sinkptr->to_string());
			}
			return std::make_shared<detail::SimpleFileSink>(filename, truncate);
		}

		SinkPtr new_rotate_file_sink(std::string filename, std::size_t maxSizeInByte, bool backupOld)
		{
			auto sinkptr = get_sink(os::absolute_path(filename));
			if (sinkptr)
			{
				throw RuntimeException("File: " + filename + " already holded by another sink!\n" + sinkptr->to_string());
			}
			return std::make_shared<detail::RotateFileSink>(filename, maxSizeInByte, backupOld);
		}

		void Logger::attach_sink_list(std::vector<std::string> &sinkList)
		{
			for (auto sinkname : sinkList)
			{
				SinkPtr psink = nullptr;
				if (sinkname == consts::kStdoutSinkName)
				{
					psink = new_stdout_sink();
				}
				else if (sinkname == consts::kStderrSinkName)
				{
					psink = new_stderr_sink();
				}
				else
				{
					psink = log::get_sink(sinkname);
				}
				if (psink && (!this->get_sink(psink->name()))) attach_sink(psink);
			}
		}

		void lock_loggers()
		{
			detail::LoggerRegistry::instance().lock();
		}

		void unlock_loggers()
		{
			detail::LoggerRegistry::instance().unlock();
		}

		void drop_logger(std::string name)
		{
			detail::LoggerRegistry::instance().drop(name);
		}

		void drop_all_loggers()
		{
			detail::LoggerRegistry::instance().drop_all();
		}

		void drop_sink(std::string name)
		{
			SinkPtr psink = get_sink(name);
			if (nullptr == psink) return;
			auto loggers = detail::LoggerRegistry::instance().get_all();
			for (auto logger : loggers)
			{
				logger->detach_sink(psink);
			}
		}

		void detail::config_from_parser(cfg::CfgParser& parser)
		{
			// config for specific sinks
			auto sinkSection = parser(consts::KConfigSinkSectionSpecifier).sections;
			auto sinkMap = detail::config_sinks_from_section(sinkSection);

			// global format
			std::string format = parser(consts::KConfigGlobalSectionSpecifier)[consts::kConfigFormatSpecifier].str();
			if (!format.empty()) LogConfig::set_default_format(format);
			// global datetime format
			std::string datefmt = parser(consts::KConfigGlobalSectionSpecifier)[consts::kConfigDateTimeFormatSpecifier].str();
			if (!datefmt.empty()) LogConfig::set_default_datetime_format(datefmt);
			// global log levels
			auto v = parser(consts::KConfigGlobalSectionSpecifier)[consts::kConfigLevelsSpecifier];
			if (!v.str().empty()) LogConfig::set_default_level_mask(level_mask_from_string(v.str()));
			// global sink list
			v = parser(consts::KConfigGlobalSectionSpecifier)[consts::kConfigSinkListSpecifier];
			if (!v.str().empty())
			{
				auto list = fmt::split_whitespace(v.str());
				detail::sink_list_revise(list, sinkMap);
				LogConfig::set_default_sink_list(list);
			}

			// config for specific loggers
			auto loggerSection = parser(consts::KConfigLoggerSectionSpecifier).sections;
			detail::config_loggers_from_section(loggerSection, sinkMap);
		}

		void config_from_file(std::string cfgFilename)
		{
			cfg::CfgParser parser(cfgFilename);
			detail::config_from_parser(parser);
		}

		void config_from_stringstream(std::stringstream& ss)
		{
			cfg::CfgParser parser(ss);
			detail::config_from_parser(parser);
		}

	} // namespace log

	Image::Image(const char* filename)
	{
		load(filename);
	}

	void Image::load(const char* filename)
	{
		int x;
		int y;
		int comp;
		Image::value_type *buffer = nullptr;
		buffer = thirdparty::stbi::decode::stbi_load(filename, &x, &y, &comp, 0);
		if (!buffer)
		{
			std::string msg = "Failed to load from " + std::string(filename) + ": ";
			msg += thirdparty::stbi::decode::stbi_failure_reason();
			throw RuntimeException(msg);
		};
		import(buffer, y, x, comp);
		thirdparty::stbi::decode::stbi_image_free(buffer);
	}

	void Image::save(const char* filename, int quality) const
	{
		std::string ext = fmt::to_lower_ascii(os::path_split_extension(filename));
		if (ext == "jpg" || ext == "jpeg")
		{
			thirdparty::jo::jo_write_jpg(filename, (*data_).data(), cols_, rows_, channels_, quality);
		}
		else if (ext == "png")
		{
			thirdparty::stbi::encode::stbi_write_png(filename, cols_, rows_, channels_, (*data_).data(), cols_ * channels_);
		}
		else if (ext == "bmp")
		{
			thirdparty::stbi::encode::stbi_write_bmp(filename, cols_, rows_, channels_, (*data_).data());
		}
		else if (ext == "tga")
		{
			thirdparty::stbi::encode::stbi_write_tga(filename, cols_, rows_, channels_, (*data_).data());
		}
		else
		{
			throw ArgException("Not supported format: " + ext);
		}
	}

	void Image::resize(int width, int height)
	{
		assert(height > 0 && "height must > 0!");
		assert(width > 0 && "width must > 0!");
		range_check(0);
		detach();
		std::shared_ptr<std::vector<Image::value_type>> buf = std::make_shared<std::vector<Image::value_type>> (height * width * channels_);
		thirdparty::stbi::resize::stbir_resize_uint8(&(*data_).front(), cols_, rows_, 0, &(*buf).front(), width, height, 0, channels_);
		data_ = buf;
		rows_ = height;
		cols_ = width;
	}

	void Image::resize(double ratio)
	{
		assert(ratio > 0 && "resize ratio must > 0!");
		int width = static_cast<int>(cols_ * ratio);
		int height = static_cast<int>(rows_ * ratio);
		resize(height, width);
	}

	void Image::resize(Size sz)
	{
		resize(sz.height, sz.width);
	}

	ImageHdr::ImageHdr(const char* filename)
	{
		load(filename);
	}

	ImageHdr::ImageHdr(const Image& from, float range)
	{
		from_normal(from, range);
	}

	void ImageHdr::load(const char* filename)
	{
		int x;
		int y;
		int comp;
		ImageHdr::value_type *buffer = nullptr;
		buffer = thirdparty::stbi::decode::stbi_loadf(filename, &x, &y, &comp, 0);
		if (!buffer)
		{
			std::string msg = "Failed to load from " + std::string(filename) + ": ";
			msg += thirdparty::stbi::decode::stbi_failure_reason();
			throw RuntimeException(msg);
		};
		import(buffer, y, x, comp);
		thirdparty::stbi::decode::stbi_image_free(buffer);
	}

	void ImageHdr::save_hdr(const char* filename) const
	{
		thirdparty::stbi::encode::stbi_write_hdr(filename, cols_, rows_, channels_, (*data_).data());
	}

	Image ImageHdr::to_normal(float range) const
	{
		Image tmp(rows_, cols_, channels_);
		auto p = tmp.ptr();
		for (auto iter = (*data_).cbegin(); iter != (*data_).cend(); ++iter)
		{
			*p = saturate_cast<Image::value_type>(*iter / range * 255);
			++p;
		}
		return tmp;
	}

	void ImageHdr::from_normal(const Image& from, float range)
	{
		create(from.rows(), from.cols(), from.channels());
		auto p = from.ptr();
		for (auto iter = (*data_).begin(); iter != (*data_).end(); ++iter)
		{
			*iter = saturate_cast<value_type>(*p / 255.0f * range);
			++p;
		}
	}

	void ImageHdr::resize(int width, int height)
	{
		assert(height > 0 && "height must > 0!");
		assert(width > 0 && "width must > 0!");
		range_check(0);
		detach();
		std::shared_ptr<std::vector<ImageHdr::value_type>> buf = std::make_shared<std::vector<ImageHdr::value_type>>(height * width * channels_);
		thirdparty::stbi::resize::stbir_resize_float(&(*data_).front(), cols_, rows_, 0, &(*buf).front(), width, height, 0, channels_);
		data_ = buf;
		rows_ = height;
		cols_ = width;
	}

	void ImageHdr::resize(double ratio)
	{
		assert(ratio > 0 && "resize ratio must > 0!");
		int width = static_cast<int>(cols_ * ratio);
		int height = static_cast<int>(rows_ * ratio);
		resize(height, width);
	}

	void ImageHdr::resize(Size sz)
	{
		resize(sz.height, sz.width);
	}

} // end namesapce zz
